<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://ikewalker.github.io/flite-mechanics/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ikewalker.github.io/flite-mechanics/" rel="alternate" type="text/html" /><updated>2026-08-24T20:11:21+00:00</updated><id>https://ikewalker.github.io/flite-mechanics/feed.xml</id><title type="html">Mechanics of Flite</title><subtitle>Engineering [at] Flite</subtitle><author><name>Flite Inc</name></author><entry><title type="html">Replacing JSON UDF calls with native function calls in MySQL 5.7</title><link href="https://ikewalker.github.io/flite-mechanics/blog/2016/10/03/replacing-json-udf-calls-with-native-function-calls-in-mysql-5-dot-7/" rel="alternate" type="text/html" title="Replacing JSON UDF calls with native function calls in MySQL 5.7" /><published>2016-10-03T14:22:00+00:00</published><updated>2016-10-03T14:22:00+00:00</updated><id>https://ikewalker.github.io/flite-mechanics/blog/2016/10/03/replacing-json-udf-calls-with-native-function-calls-in-mysql-5-dot-7</id><content type="html" xml:base="https://ikewalker.github.io/flite-mechanics/blog/2016/10/03/replacing-json-udf-calls-with-native-function-calls-in-mysql-5-dot-7/"><![CDATA[<p>If you use JSON UDFs in MySQL 5.6 there are a few things to consider before upgrading to MySQL 5.7. From a schema perspective, if you have JSON stored in text columns you can decide when to convert those columns to the new JSON datatype after you upgrade, but due to namespace collisions and format differences it’s probably not safe to upgrade without dropping some or all of the existing UDFs and updating your queries that use those UDFs.</p>

<p>Read on for details…</p>

<!--more-->

<h2 id="namespace-collisions">Namespace collisions</h2>
<p>There are 15 functions in JSON UDFs version 0.4.0. Nine of those names are used by native JSON functions in MySQL 5.7, so it’s not possible to continue using those nine UDFs in MySQL 5.7. Therefore before you upgrade you should probably drop these UDFs:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>DROP FUNCTION IF EXISTS json_append;
DROP FUNCTION IF EXISTS json_depth;
DROP FUNCTION IF EXISTS json_extract;
DROP FUNCTION IF EXISTS json_merge;
DROP FUNCTION IF EXISTS json_remove;
DROP FUNCTION IF EXISTS json_replace;
DROP FUNCTION IF EXISTS json_search;
DROP FUNCTION IF EXISTS json_set;
DROP FUNCTION IF EXISTS json_valid;
</code></pre></div></div>

<h2 id="path-syntax">Path syntax</h2>
<p>Depending on which version of MySQL JSON UDFs you are using, you may need to rewrite your queries to use the new path syntax. If you are using version 0.2 or 0.3 then you’re using this path syntax:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>json_extract(doc, keypart1, keypart2, ...)
</code></pre></div></div>

<p>For example:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; select json_extract('{"foo":"bar"}','foo');
+-------------------------------------+
| json_extract('{"foo":"bar"}','foo') |
+-------------------------------------+
| bar                                 |
+-------------------------------------+
1 row in set (0.00 sec)

mysql&gt; select json_extract('{"parent":{"child":"hello"}}','parent','child');
+---------------------------------------------------------------+
| json_extract('{"parent":{"child":"hello"}}','parent','child') |
+---------------------------------------------------------------+
| hello                                                         |
+---------------------------------------------------------------+
1 row in set (0.00 sec)
</code></pre></div></div>

<p>The native JSON functions use this path syntax:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>JSON_EXTRACT(json_doc, path[, path] ...)
</code></pre></div></div>

<p>For example:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; select json_extract('{"foo":"bar"}','$.foo');
+---------------------------------------+
| json_extract('{"foo":"bar"}','$.foo') |
+---------------------------------------+
| "bar"                                 |
+---------------------------------------+
1 row in set (0.00 sec)

mysql&gt; select json_extract('{"parent":{"child":"hello"}}','$.parent.child');
+---------------------------------------------------------------+
| json_extract('{"parent":{"child":"hello"}}','$.parent.child') |
+---------------------------------------------------------------+
| "hello"                                                       |
+---------------------------------------------------------------+
1 row in set (0.00 sec)
</code></pre></div></div>

<p>MySQL JSON UDFs version 0.4 had two copies of each UDF – one for each path syntax – so you only need to rewrite this part of your queries if you’re using the old path syntax. The functions using the old path syntax are in <code class="language-plaintext highlighter-rouge">libmy_json_udf</code> and the functions using the new path syntax are in <code class="language-plaintext highlighter-rouge">libmy_json_udf_path</code>.</p>

<h2 id="input-differences">Input differences</h2>
<p>The MySQL 5.7 native JSON functions have some input differences as compared to the JSON UDF functions with the same name. One fundamental difference is the support for input strings which are not valid JSON. In general, the UDFs return <code class="language-plaintext highlighter-rouge">NULL</code> when the input is not valid JSON, whereas the native functions return an error. If your input is a column of JSON datatype then this is not a concern, since the datatype enforces the validity of the JSON and will not store invalid JSON. If the input is text you need to be more careful.</p>

<p>For example, none of these queries throw an error in MySQL 5.6 using JSON UDFs:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; select json_extract('','foo');
+------------------------+
| json_extract('','foo') |
+------------------------+
| NULL                   |
+------------------------+
1 row in set (0.00 sec)

mysql&gt; select json_extract('NOT JSON','foo');
+--------------------------------+
| json_extract('NOT JSON','foo') |
+--------------------------------+
| NULL                           |
+--------------------------------+
1 row in set (0.00 sec)
</code></pre></div></div>

<p>You can simply use <code class="language-plaintext highlighter-rouge">COALESCE()</code> if you want to replace the NULL output with the empty string:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; select coalesce(json_extract('NOT JSON','foo'),'');
+---------------------------------------------+
| coalesce(json_extract('NOT JSON','foo'),'') |
+---------------------------------------------+
|                                             |
+---------------------------------------------+
1 row in set (0.00 sec)
</code></pre></div></div>

<p>But in MySQL 5.7 with native functions these queries throw an error:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; select json_extract('','$.foo');
ERROR 3141 (22032): Invalid JSON text in argument 1 to function json_extract: "The document is empty." at position 0.
mysql&gt; select json_extract('NOT JSON','$.foo');
ERROR 3141 (22032): Invalid JSON text in argument 1 to function json_extract: "Invalid value." at position 0.
</code></pre></div></div>

<p>One way around that is using a <code class="language-plaintext highlighter-rouge">CASE</code> statement along with the <code class="language-plaintext highlighter-rouge">JSON_VALID()</code> functions:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; set @json = '';
Query OK, 0 rows affected (0.00 sec)

mysql&gt; select (case when json_valid(@json) then json_extract(@json,'$.foo') else '' end);
+----------------------------------------------------------------------------+
| (case when json_valid(@json) then json_extract(@json,'$.foo') else '' end) |
+----------------------------------------------------------------------------+
|                                                                            |
+----------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql&gt; set @json = 'NOT JSON';
Query OK, 0 rows affected (0.01 sec)

mysql&gt; select (case when json_valid(@json) then json_extract(@json,'$.foo') else '' end);
+----------------------------------------------------------------------------+
| (case when json_valid(@json) then json_extract(@json,'$.foo') else '' end) |
+----------------------------------------------------------------------------+
|                                                                            |
+----------------------------------------------------------------------------+
1 row in set (0.00 sec)
</code></pre></div></div>

<p>Since the above queries return empty string if they encounter invalid JSON there is no need to use <code class="language-plaintext highlighter-rouge">COALESCE()</code>.</p>

<h2 id="output-differences">Output differences</h2>
<p>The MySQL 5.7 native JSON functions return JSON objects, whereas the JSON UDFs return text. For example a call to the <code class="language-plaintext highlighter-rouge">JSON_EXTRACT()</code> UDF in MySQL 5.6 may return <code class="language-plaintext highlighter-rouge">foo</code>, but the same function in MySQL 5.7 would return <code class="language-plaintext highlighter-rouge">"foo"</code>. Therefore in order to replace a call to the UDF version of a function like <code class="language-plaintext highlighter-rouge">JSON_EXTRACT()</code> with the corresponding native function call, you should wrap that call with the <a href="https://dev.mysql.com/doc/refman/5.7/en/json-modification-functions.html#function_json-unquote"><code class="language-plaintext highlighter-rouge">JSON_UNQUOTE()</code></a> function.</p>

<p>Examples:</p>

<p>###MySQL 5.6</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; select json_extract('{"foo":"bar"}','foo');
+-------------------------------------+
| json_extract('{"foo":"bar"}','foo') |
+-------------------------------------+
| bar                                 |
+-------------------------------------+
1 row in set (0.00 sec)
</code></pre></div></div>

<p>###MySQL 5.7</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; select json_extract('{"foo":"bar"}','$.foo');
+---------------------------------------+
| json_extract('{"foo":"bar"}','$.foo') |
+---------------------------------------+
| "bar"                                 |
+---------------------------------------+
1 row in set (0.12 sec)

mysql&gt; select json_unquote(json_extract('{"foo":"bar"}','$.foo'));
+-----------------------------------------------------+
| json_unquote(json_extract('{"foo":"bar"}','$.foo')) |
+-----------------------------------------------------+
| bar                                                 |
+-----------------------------------------------------+
1 row in set (0.00 sec)
</code></pre></div></div>

<p>While you are re-writing those function calls, consider using the <code class="language-plaintext highlighter-rouge">-&gt;</code> (added in 5.7.9) and <code class="language-plaintext highlighter-rouge">-&gt;&gt;</code> (added in 5.7.13) operators, which are shortcuts for <code class="language-plaintext highlighter-rouge">JSON_EXTRACT()</code> and <code class="language-plaintext highlighter-rouge">JSON_UNQUOTE(JSON_EXTRACT())</code> respectively. For example, these three statements are equivalent:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select JSON_UNQUOTE( JSON_EXTRACT(my_column, '$.some_key') )
from my_table;

select JSON_UNQUOTE(my_column -&gt; '$.some_key')
from my_table;

select my_column-&gt;&gt;'$.some_key'
from my_table;
</code></pre></div></div>

<p>Testing those out to confirm the output:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; create temporary table if not exists json_test as select '{"foo":"bar"}' as json_val;
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql&gt; select json_unquote(json_extract(json_val,'$.foo')) from json_test;
+----------------------------------------------+
| json_unquote(json_extract(json_val,'$.foo')) |
+----------------------------------------------+
| bar                                          |
+----------------------------------------------+
1 row in set (0.03 sec)

mysql&gt; select json_unquote(json_val-&gt;'$.foo') from json_test;
+---------------------------------+
| json_unquote(json_val-&gt;'$.foo') |
+---------------------------------+
| bar                             |
+---------------------------------+
1 row in set (0.00 sec)

mysql&gt; select json_val-&gt;&gt;'$.foo' from json_test;
+--------------------+
| json_val-&gt;&gt;'$.foo' |
+--------------------+
| bar                |
+--------------------+
1 row in set (0.00 sec)
</code></pre></div></div>]]></content><author><name>Ike Walker</name></author><category term="MySQL" /><category term="JSON" /><summary type="html"><![CDATA[If you use JSON UDFs in MySQL 5.6 there are a few things to consider before upgrading to MySQL 5.7. From a schema perspective, if you have JSON stored in text columns you can decide when to convert those columns to the new JSON datatype after you upgrade, but due to namespace collisions and format differences it’s probably not safe to upgrade without dropping some or all of the existing UDFs and updating your queries that use those UDFs. Read on for details…]]></summary></entry><entry><title type="html">Speaking at Percona Live Amsterdam 2016</title><link href="https://ikewalker.github.io/flite-mechanics/blog/2016/09/21/speaking-at-percona-live-amsterdam-2016/" rel="alternate" type="text/html" title="Speaking at Percona Live Amsterdam 2016" /><published>2016-09-21T15:33:00+00:00</published><updated>2016-09-21T15:33:00+00:00</updated><id>https://ikewalker.github.io/flite-mechanics/blog/2016/09/21/speaking-at-percona-live-amsterdam-2016</id><content type="html" xml:base="https://ikewalker.github.io/flite-mechanics/blog/2016/09/21/speaking-at-percona-live-amsterdam-2016/"><![CDATA[<p>I look forward to speaking at <a href="https://www.percona.com/live/plam16/">Percona Live Amsterdam 2016</a> just under two weeks from now.</p>

<p>Here is my talk:</p>

<ul>
  <li><a href="https://www.percona.com/live/plam16/sessions/practical-json-mysql-57">Practical JSON in MySQL 5.7</a></li>
</ul>

<p>To help organize my past (and future) blog posts about using JSON in MySQL, I am now tagging those posts with “JSON”, so you can find them <a href="http://mechanics.flite.com/blog/categories/json/">here</a>.</p>

<p>See you in Amsterdam!</p>]]></content><author><name>Ike Walker</name></author><category term="MySQL" /><summary type="html"><![CDATA[I look forward to speaking at Percona Live Amsterdam 2016 just under two weeks from now. Here is my talk: Practical JSON in MySQL 5.7 To help organize my past (and future) blog posts about using JSON in MySQL, I am now tagging those posts with “JSON”, so you can find them here. See you in Amsterdam!]]></summary></entry><entry><title type="html">Avoiding MySQL ERROR 1784 when replicating from 5.7 to 5.6</title><link href="https://ikewalker.github.io/flite-mechanics/blog/2016/07/22/avoiding-mysql-error-1784-when-replicating-from-5-dot-7-to-5-dot-6/" rel="alternate" type="text/html" title="Avoiding MySQL ERROR 1784 when replicating from 5.7 to 5.6" /><published>2016-07-22T16:50:00+00:00</published><updated>2016-07-22T16:50:00+00:00</updated><id>https://ikewalker.github.io/flite-mechanics/blog/2016/07/22/avoiding-mysql-error-1784-when-replicating-from-5-dot-7-to-5-dot-6</id><content type="html" xml:base="https://ikewalker.github.io/flite-mechanics/blog/2016/07/22/avoiding-mysql-error-1784-when-replicating-from-5-dot-7-to-5-dot-6/"><![CDATA[<p>Recently I upgraded some MySQL databases from 5.6 to 5.7, but – for boring reasons unique to my environment – I had to leave one replica on version 5.6. I knew there was a chance that the 5.7 -&gt; 5.6 replication wouldn’t work, but I decided to try it out to see if (and why) it would fail. Once I upgraded the master, replication failed, so I checked the error log on the replica and found these messages:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ERROR] Slave I/O: Found a Gtid_log_event or Previous_gtids_log_event when @@GLOBAL.GTID_MODE = OFF. Error_code: 1784
[ERROR] Slave I/O: Relay log write failure: could not queue event from master, Error_code: 1595
</code></pre></div></div>

<p>The error surprised me a little bit since I’m not using GTIDs in that replication topology. I asked around a bit, and Kenny Gryp hypothesized that I might be experiencing <a href="https://bugs.mysql.com/bug.php?id=74683">MySQL bug #74683</a>, which was fixed in version 5.6.23. Since my replica was on 5.6.22, I decided to do an incremental 5.6 upgrade to see if that resolved the issue. I upgraded to 5.6.31 and replication started working.</p>

<p>YMMV and there are certainly bound to be 5.7 -&gt; 5.6.31 replication scenarios that don’t work, but this was a simple fix for me. In hindsight it makes sense that replicating from a new major version to an older major version is more likely to work with a more recent minor version, so if I have to do this in the future I’ll make sure the replica is running the latest minor version before upgrading the master.</p>

<p>P.S. Thanks Kenny!</p>]]></content><author><name>Ike Walker</name></author><category term="MySQL" /><summary type="html"><![CDATA[Recently I upgraded some MySQL databases from 5.6 to 5.7, but – for boring reasons unique to my environment – I had to leave one replica on version 5.6. I knew there was a chance that the 5.7 -&gt; 5.6 replication wouldn’t work, but I decided to try it out to see if (and why) it would fail. Once I upgraded the master, replication failed, so I checked the error log on the replica and found these messages: [ERROR] Slave I/O: Found a Gtid_log_event or Previous_gtids_log_event when @@GLOBAL.GTID_MODE = OFF. Error_code: 1784 [ERROR] Slave I/O: Relay log write failure: could not queue event from master, Error_code: 1595 The error surprised me a little bit since I’m not using GTIDs in that replication topology. I asked around a bit, and Kenny Gryp hypothesized that I might be experiencing MySQL bug #74683, which was fixed in version 5.6.23. Since my replica was on 5.6.22, I decided to do an incremental 5.6 upgrade to see if that resolved the issue. I upgraded to 5.6.31 and replication started working. YMMV and there are certainly bound to be 5.7 -&gt; 5.6.31 replication scenarios that don’t work, but this was a simple fix for me. In hindsight it makes sense that replicating from a new major version to an older major version is more likely to work with a more recent minor version, so if I have to do this in the future I’ll make sure the replica is running the latest minor version before upgrading the master. P.S. Thanks Kenny!]]></summary></entry><entry><title type="html">Column Level Privileges in MySQL</title><link href="https://ikewalker.github.io/flite-mechanics/blog/2016/06/30/column-level-privileges-in-mysql/" rel="alternate" type="text/html" title="Column Level Privileges in MySQL" /><published>2016-06-30T21:33:00+00:00</published><updated>2016-06-30T21:33:00+00:00</updated><id>https://ikewalker.github.io/flite-mechanics/blog/2016/06/30/column-level-privileges-in-mysql</id><content type="html" xml:base="https://ikewalker.github.io/flite-mechanics/blog/2016/06/30/column-level-privileges-in-mysql/"><![CDATA[<p>Recently I experimented with <a href="http://dev.mysql.com/doc/refman/5.6/en/grant.html#grant-column-privileges">column-level privileges</a> in MySQL. Column-level privileges are fairly straightforward, but given how infrequently they are used I think there are a few areas worth discussing.</p>

<p>Here are a few high-level observations:</p>

<ul>
  <li>Users can execute <code class="language-plaintext highlighter-rouge">INSERT</code> and <code class="language-plaintext highlighter-rouge">UPDATE</code> statements that affect columns they don’t have privileges on, as long as they rely on implicit defaults</li>
  <li>Since SQL is row-based, it doesn’t make sense to support column-level <code class="language-plaintext highlighter-rouge">DELETE</code> privileges, thus only <code class="language-plaintext highlighter-rouge">SELECT</code>, <code class="language-plaintext highlighter-rouge">INSERT</code>, and <code class="language-plaintext highlighter-rouge">UPDATE</code> are supported</li>
  <li>You can grant privileges on multiple columns in one <code class="language-plaintext highlighter-rouge">GRANT</code> statement or multiple <code class="language-plaintext highlighter-rouge">GRANT</code> statements, the results are cumulative</li>
</ul>

<p>Read on for more details on each type of column-level privilege, along with example queries.</p>

<!--more-->

<h2 id="select">SELECT</h2>

<p>Users may only reference columns that they have explicity privileges on. This applies to the entire <code class="language-plaintext highlighter-rouge">SELECT</code> statement, not just the <code class="language-plaintext highlighter-rouge">SELECT</code> clause. If you try to reference a column that you do not have privileges on in the <code class="language-plaintext highlighter-rouge">WHERE</code>, <code class="language-plaintext highlighter-rouge">GROUP BY</code>, <code class="language-plaintext highlighter-rouge">HAVING</code>, or <code class="language-plaintext highlighter-rouge">ORDER BY</code> clause then you will get an error.</p>

<p>To illustrate this I created a table with two rows of sample data for testing:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; create table good_stuff (
    -&gt;   id int unsigned not null auto_increment primary key,
    -&gt;   ctime timestamp default current_timestamp,
    -&gt;   mtime timestamp default current_timestamp on update current_timestamp,
    -&gt;   is_deleted tinyint not null default 0,
    -&gt;   public varchar(255) null,
    -&gt;   protected varchar(255) null,
    -&gt;   private varchar(255) null
    -&gt; ) engine = innodb;
Query OK, 0 rows affected (0.03 sec)

mysql&gt; insert into good_stuff (id,public,protected,private) 
    -&gt; values (DEFAULT,'Hello world!','Red','Secret');
Query OK, 1 row affected (0.00 sec)

mysql&gt; insert into good_stuff (id,public,protected,private)
    -&gt; values (DEFAULT,'Hi Scott','Blue','Boo Scott');
Query OK, 1 row affected (0.01 sec)
</code></pre></div></div>

<p>If I grant <code class="language-plaintext highlighter-rouge">SELECT</code> privileges on two columns in that table to a user named “scott”, then scott may select those two columns:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; -- as root, grant SELECT privileges to scott
mysql&gt; grant select (public,protected) on good_stuff to scott@'%';
Query OK, 0 rows affected (0.00 sec)
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; -- as scott, test SELECT privileges
mysql&gt; select public,protected from good_stuff;
+--------------+-----------+
| public       | protected |
+--------------+-----------+
| Hello world! | Red       |
| Hi Scott     | Blue      |
+--------------+-----------+
2 rows in set (0.00 sec)
</code></pre></div></div>

<p>But scott may not reference another column in the <code class="language-plaintext highlighter-rouge">ORDER BY</code> clause:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; -- as scott, test SELECT privileges 
mysql&gt; select public from good_stuff order by id;
ERROR 1143 (42000): SELECT command denied to user 'scott'@'localhost' for column 'id' in table 'good_stuff'
</code></pre></div></div>

<p>Table-level privileges take precendence, so granting table-level <code class="language-plaintext highlighter-rouge">SELECT</code> privileges to a user overrides any column-level <code class="language-plaintext highlighter-rouge">SELECT</code> privileges they may have.</p>

<p>If you happen to grant <code class="language-plaintext highlighter-rouge">SELECT</code> privileges on all columns in a table to a user, then the user is allowed to run <code class="language-plaintext highlighter-rouge">SELECT *</code> queries.</p>

<h2 id="insert">INSERT</h2>

<p>Users may only explicitly insert data into columns for which they have the <code class="language-plaintext highlighter-rouge">INSERT</code> privilege. <code class="language-plaintext highlighter-rouge">INSERT</code> privileges do not rely on <code class="language-plaintext highlighter-rouge">SELECT</code> privileges, so it is possible to configure a user who may write data but not read it. Default values may be used, but only implicitly. If you try to explicitly reference a default value you will get an error.</p>

<p>If I give scott <code class="language-plaintext highlighter-rouge">INSERT</code> privileges on only the <code class="language-plaintext highlighter-rouge">public</code> column in my table, he can still insert a row as long as he only references that one column, and default values will be used for other columns (<code class="language-plaintext highlighter-rouge">id, ctime, mtime, is_deleted</code>).</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; -- as root, grant INSERT privileges to scott
mysql&gt; grant insert (public) on good_stuff to scott@'%';
Query OK, 0 rows affected (0.00 sec)
</code></pre></div></div>

<p>For example this works:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; -- as scott, test INSERT privileges 
mysql&gt; insert into good_stuff (public) values ('Hi everybody');
Query OK, 1 row affected (0.01 sec)
</code></pre></div></div>

<p>And results in a row like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; -- as root, select full row that scott inserted
mysql&gt; select * from good_stuff where public = 'Hi everybody';
+----+---------------------+---------------------+------------+--------------+-----------+---------+
| id | ctime               | mtime               | is_deleted | public       | protected | private |
+----+---------------------+---------------------+------------+--------------+-----------+---------+
|  3 | 2016-06-30 20:37:39 | 2016-06-30 20:37:39 |          0 | Hi everybody | NULL      | NULL    |
+----+---------------------+---------------------+------------+--------------+-----------+---------+
</code></pre></div></div>

<p>These statements all fail even though they have the same intent, because they are explicitly referencing columns that scott does not have privileges to <code class="language-plaintext highlighter-rouge">INSERT</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; -- as scott, test INSERT privileges
mysql&gt; insert into good_stuff (id,public) values (null,'Is it okay if I do this?');
ERROR 1143 (42000): INSERT command denied to user 'scott'@'localhost' for column 'id' in table 'good_stuff'
mysql&gt; insert into good_stuff (id,public) values (DEFAULT,'Is it okay if I do this?');
ERROR 1143 (42000): INSERT command denied to user 'scott'@'localhost' for column 'id' in table 'good_stuff'
</code></pre></div></div>

<p>I would get the same results if I tried to use <code class="language-plaintext highlighter-rouge">null</code> or <code class="language-plaintext highlighter-rouge">DEFAULT</code> with the other columns that have defaults values (<code class="language-plaintext highlighter-rouge">ctime, mtime, is_deleted</code>).</p>

<h2 id="update">UPDATE</h2>

<p><code class="language-plaintext highlighter-rouge">UPDATE</code> statements support column-level privileges much the same way as <code class="language-plaintext highlighter-rouge">SELECT</code> and <code class="language-plaintext highlighter-rouge">INSERT</code>. In order to explicitly update a column a user needs <code class="language-plaintext highlighter-rouge">UPDATE</code> privileges on that column, but columns can be set to default values implicitly. If you reference a column in the <code class="language-plaintext highlighter-rouge">WHERE</code> clause of an <code class="language-plaintext highlighter-rouge">UPDATE</code>, then you need <code class="language-plaintext highlighter-rouge">SELECT</code> privileges on that column.</p>

<p>First I grant <code class="language-plaintext highlighter-rouge">UPDATE</code> privileges on the <code class="language-plaintext highlighter-rouge">public</code> column to scott:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; -- as root, grant UPDATE privileges to scott
mysql&gt; grant update (public) on good_stuff to scott@'%';
Query OK, 0 rows affected (0.01 sec)
</code></pre></div></div>

<p>This is allowed:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; -- as scott, test UPDATE privileges
mysql&gt; update good_stuff set public = lower(public);
Query OK, 1 row affected (0.00 sec)
Rows matched: 4  Changed: 1  Warnings: 0
</code></pre></div></div>

<p>This is also allowed:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; -- as scott, test UPDATE privileges
mysql&gt; update good_stuff set public = upper(public) where protected = 'Blue';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
</code></pre></div></div>

<p>Please note that the previous two statements will only work if scott has both <code class="language-plaintext highlighter-rouge">SELECT</code> and <code class="language-plaintext highlighter-rouge">UPDATE</code> privileges on the <code class="language-plaintext highlighter-rouge">public</code> column, since the existing value is being used in the <code class="language-plaintext highlighter-rouge">UPDATE</code>, so that requires both a read and a write.</p>

<p>These are not allowed because the <code class="language-plaintext highlighter-rouge">WHERE</code> clause references a column for which scott does not have <code class="language-plaintext highlighter-rouge">SELECT</code> privileges:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; -- as scott, test UPDATE privileges
mysql&gt; update good_stuff set public = upper(public) where id = 2;
ERROR 1143 (42000): SELECT command denied to user 'scott'@'localhost' for column 'id' in table 'good_stuff'
mysql&gt; update good_stuff set is_deleted = DEFAULT;
ERROR 1143 (42000): UPDATE command denied to user 'scott'@'localhost' for column 'is_deleted' in table 'good_stuff'
</code></pre></div></div>

<p>Even though scott does not have privileges to update the <code class="language-plaintext highlighter-rouge">mtime</code> column, the <code class="language-plaintext highlighter-rouge">on update current_timestamp</code> is still invoked implicitly. To verify this I can select back the “Hi everybody” row I selected earlier to confirm that the <code class="language-plaintext highlighter-rouge">mtime</code> value changed:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; -- as root, select full row that scott inserted
mysql&gt; select * from good_stuff where id = 3;
+----+---------------------+---------------------+------------+--------------+-----------+---------+
| id | ctime               | mtime               | is_deleted | public       | protected | private |
+----+---------------------+---------------------+------------+--------------+-----------+---------+
|  3 | 2016-06-30 20:37:39 | 2016-06-30 21:15:03 |          0 | hi everybody | NULL      | NULL    |
+----+---------------------+---------------------+------------+--------------+-----------+---------+
</code></pre></div></div>

<h2 id="delete">DELETE</h2>

<p>As mentioned above, column-level <code class="language-plaintext highlighter-rouge">DELETE</code> privileges are not supported in MySQL.</p>]]></content><author><name>Ike Walker</name></author><category term="MySQL" /><summary type="html"><![CDATA[Recently I experimented with column-level privileges in MySQL. Column-level privileges are fairly straightforward, but given how infrequently they are used I think there are a few areas worth discussing. Here are a few high-level observations: Users can execute INSERT and UPDATE statements that affect columns they don’t have privileges on, as long as they rely on implicit defaults Since SQL is row-based, it doesn’t make sense to support column-level DELETE privileges, thus only SELECT, INSERT, and UPDATE are supported You can grant privileges on multiple columns in one GRANT statement or multiple GRANT statements, the results are cumulative Read on for more details on each type of column-level privilege, along with example queries.]]></summary></entry><entry><title type="html">MySQL Keywords and Reserved Words</title><link href="https://ikewalker.github.io/flite-mechanics/blog/2016/05/24/mysql-keywords-and-reserved-words/" rel="alternate" type="text/html" title="MySQL Keywords and Reserved Words" /><published>2016-05-24T16:05:00+00:00</published><updated>2016-05-24T16:05:00+00:00</updated><id>https://ikewalker.github.io/flite-mechanics/blog/2016/05/24/mysql-keywords-and-reserved-words</id><content type="html" xml:base="https://ikewalker.github.io/flite-mechanics/blog/2016/05/24/mysql-keywords-and-reserved-words/"><![CDATA[<p>I’ve seen some confusion on what constitutes a keyword or a reserved word in MySQL. The <a href="https://dev.mysql.com/doc/refman/5.6/en/keywords.html">manual</a> defines them, and has a complete list. Reserved words are a special subset of keywords, and you can’t use a reserved word as an identifier unless you quote it with backticks. I discourage the use of backticks to quote identifiers, because it allows you to use lots of words and characters in your identifier that you’ll probably regret later. For example:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; create table `You will regret this!` (`(╯°□°）╯︵ ┻━┻` int);
Query OK, 0 rows affected (0.03 sec)

mysql&gt; desc `You will regret this!`;
+--------------------------------+---------+------+-----+---------+-------+
| Field                          | Type    | Null | Key | Default | Extra |
+--------------------------------+---------+------+-----+---------+-------+
| (╯°□°）╯︵ ┻━┻                 | int(11) | YES  |     | NULL    |       |
+--------------------------------+---------+------+-----+---------+-------+
1 row in set (0.00 sec)
</code></pre></div></div>

<p>There is also an exception for table names. You can use a reserved word as a table name without quoting it with backticks as long as you prefix it with the schema name, but again I would discourage that. Here’s an example:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; create table table (id int);
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'table (id int)' at line 1
mysql&gt; create table test.table (id int);
Query OK, 0 rows affected (0.04 sec)
</code></pre></div></div>

<!--more-->

<p>Non-reserved keywords are a little bit trickier. Many of them probably <em>should</em> be reserved, but are not. For example, lots of data type names are not reserved, nor is the word “view”. Thus I can create a table like this without even having to use backticks:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>create table view (
  bit bit,
  bool bool,
  boolean boolean,
  date date,
  datetime datetime,
  enum enum('enum'),
  text text,
  time time,
  timestamp timestamp,
  year year,
  comment int comment 'int'
);
</code></pre></div></div>

<p>Yuck. I think it’s a good practice to familiarize yourself with all of the non-reserved MySQL keywords and avoid using them as identifiers, but unfortunately there is nothing to prevent you from doing so.</p>]]></content><author><name>Ike Walker</name></author><category term="MySQL" /><summary type="html"><![CDATA[I’ve seen some confusion on what constitutes a keyword or a reserved word in MySQL. The manual defines them, and has a complete list. Reserved words are a special subset of keywords, and you can’t use a reserved word as an identifier unless you quote it with backticks. I discourage the use of backticks to quote identifiers, because it allows you to use lots of words and characters in your identifier that you’ll probably regret later. For example: mysql&gt; create table `You will regret this!` (`(╯°□°）╯︵ ┻━┻` int); Query OK, 0 rows affected (0.03 sec) mysql&gt; desc `You will regret this!`; +--------------------------------+---------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------------------------+---------+------+-----+---------+-------+ | (╯°□°）╯︵ ┻━┻ | int(11) | YES | | NULL | | +--------------------------------+---------+------+-----+---------+-------+ 1 row in set (0.00 sec) There is also an exception for table names. You can use a reserved word as a table name without quoting it with backticks as long as you prefix it with the schema name, but again I would discourage that. Here’s an example: mysql&gt; create table table (id int); ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'table (id int)' at line 1 mysql&gt; create table test.table (id int); Query OK, 0 rows affected (0.04 sec)]]></summary></entry><entry><title type="html">Being Naive About Folder Paths</title><link href="https://ikewalker.github.io/flite-mechanics/blog/2016/04/29/being-naive-about-folder-paths/" rel="alternate" type="text/html" title="Being Naive About Folder Paths" /><published>2016-04-29T13:41:00+00:00</published><updated>2016-04-29T13:41:00+00:00</updated><id>https://ikewalker.github.io/flite-mechanics/blog/2016/04/29/being-naive-about-folder-paths</id><content type="html" xml:base="https://ikewalker.github.io/flite-mechanics/blog/2016/04/29/being-naive-about-folder-paths/"><![CDATA[<p>Recently I worked on a feature that involved displaying a list of Folders using an auto-complete field. A simple enough task, but one that had a bit of complexity behind it. For starters, each organization in our system has their own “folder hierarchy”, or a tree structure of folders, that could be one to many layers deep. The second thing being that the UI we wanted – auto-complete – meant that we would need to “search” through the folders to find a match (based on the user’s input) as well as display the path information (where the folder lives) so that the user can differentiate folders if they happen to have the same or similar name. Since it was auto-complete, the searching also needed to be fast or else the user experience would be annoying.</p>

<p><img src="/images/folder_autocomplete.png" title="Folder selection UI." width="500" /></p>

<!-- more -->

<p>The folder tree structure was stored in a MySQL DB table in a very typical self-referencing schema that represented hierarchical data. Each row of data could have a column that referenced the “parent” folder id and if there wasn’t one, then it was a “root” folder. Each row in the table could be represented by an object in Java called Folder. Getting folders by ID and getting them in a tree-like structure was pretty straight forward. However, the path-string wasn’t part of the deal.</p>

<p>My first thought on this was that the paths would need to be pre-computed ahead of time so that they wouldn’t need to be computed every time a user typed in a letter, an expensive and slow function. One way to do it would be to have some application code insert to a table that contained the folder paths whenever a folder was saved. Our resident MySQL expert, Ike Walker, suggested I look into the closure table pattern, discussed in Chapter 3, Naive Trees, of Bill Karwin’s book, <a href="https://pragprog.com/book/bksqla/sql-antipatterns">“SQL Antipatterns”</a>. Yes, this was exactly what I was thinking of! However, as I thought about it more there were some things I didn’t quite like about it. First, there was already a lot of existing data. Using Closure Tables would mean that I would need to script something to populate the table storing the folder paths, and run it at least once. The fact that I would need to script something like that also made me think about how will this table get updated? In the book “SQL Antipatterns”, a discussion thread example was used. In the case of a discussion thread, individual comments have the same kind of tree-structure that folders might have, but comments and replies typically don’t move around whereas folders do. Updating the folder paths would be required to keep in sync with the underlying tree, along with whole sections of a particular folder’s branch. The thought of having to write this updating code made me think twice about my initial approach.</p>

<p>##Caching to the rescue</p>

<p>So what does a developer do when there’s a problem with performance? Cache. So I thought: how can caching help me here? As I pointed out earlier, there was already a Java object – Folder – and a way to get the folders in a tree structure. If the Folder had a field to store the path, then I’m halfway there, right? But wait, doesn’t that mean I’d be storing the path in the DB again? Well, not if I set it on a transient field and set it only when the tree structure is getting built up. Hold on – doesn’t that mean that the paths still need to be calculated every time we search the tree? Here’s where the caching bit comes in. The folder tree would be traversed once and as it is recursing, the paths get computed and stored on a transient field of the Folder object. At the same time, the Folders are stored in a flat List that can be used for iterating and searching, etc. Next, the method which retrieves the folder list is cached and can be cached with the Organization’s ID in the cache-key so that each list can be kept separately. To make sure we don’t cache large lists of heavy Folder objects, I cached only lists of Folder IDs (not objects) and made the Folder objects also cached by their ID. Okay, then what about the updating part I talked about? The cached list of Folders would be invalidated whenever a Folder update occurred. It means that the paths would need to be re-computed again, but only once until the next update and that seemed reasonable enough.</p>

<p>In the end, I didn’t use the closure table, although I’m keen on using it if the opportunity arises. One of the key things to coming up with the solution was knowing not only the data-structure but also knowing the nature of the data (how it’s used and how often does it tend to change). In all fairness, Bill Karwin also points out frequent updating as a weakness/pain-point of the Closure Table pattern. So there you have it my friends, use those clousure tables if you can, and if you can’t, then hopefully the approach I used here will give you some ideas!</p>]]></content><author><name>Grant Lee</name></author><category term="MySQL" /><category term="Java" /><summary type="html"><![CDATA[Recently I worked on a feature that involved displaying a list of Folders using an auto-complete field. A simple enough task, but one that had a bit of complexity behind it. For starters, each organization in our system has their own “folder hierarchy”, or a tree structure of folders, that could be one to many layers deep. The second thing being that the UI we wanted – auto-complete – meant that we would need to “search” through the folders to find a match (based on the user’s input) as well as display the path information (where the folder lives) so that the user can differentiate folders if they happen to have the same or similar name. Since it was auto-complete, the searching also needed to be fast or else the user experience would be annoying. The folder tree structure was stored in a MySQL DB table in a very typical self-referencing schema that represented hierarchical data. Each row of data could have a column that referenced the “parent” folder id and if there wasn’t one, then it was a “root” folder. Each row in the table could be represented by an object in Java called Folder. Getting folders by ID and getting them in a tree-like structure was pretty straight forward. However, the path-string wasn’t part of the deal. My first thought on this was that the paths would need to be pre-computed ahead of time so that they wouldn’t need to be computed every time a user typed in a letter, an expensive and slow function. One way to do it would be to have some application code insert to a table that contained the folder paths whenever a folder was saved. Our resident MySQL expert, Ike Walker, suggested I look into the closure table pattern, discussed in Chapter 3, Naive Trees, of Bill Karwin’s book, “SQL Antipatterns”. Yes, this was exactly what I was thinking of! However, as I thought about it more there were some things I didn’t quite like about it. First, there was already a lot of existing data. Using Closure Tables would mean that I would need to script something to populate the table storing the folder paths, and run it at least once. The fact that I would need to script something like that also made me think about how will this table get updated? In the book “SQL Antipatterns”, a discussion thread example was used. In the case of a discussion thread, individual comments have the same kind of tree-structure that folders might have, but comments and replies typically don’t move around whereas folders do. Updating the folder paths would be required to keep in sync with the underlying tree, along with whole sections of a particular folder’s branch. The thought of having to write this updating code made me think twice about my initial approach. ##Caching to the rescue So what does a developer do when there’s a problem with performance? Cache. So I thought: how can caching help me here? As I pointed out earlier, there was already a Java object – Folder – and a way to get the folders in a tree structure. If the Folder had a field to store the path, then I’m halfway there, right? But wait, doesn’t that mean I’d be storing the path in the DB again? Well, not if I set it on a transient field and set it only when the tree structure is getting built up. Hold on – doesn’t that mean that the paths still need to be calculated every time we search the tree? Here’s where the caching bit comes in. The folder tree would be traversed once and as it is recursing, the paths get computed and stored on a transient field of the Folder object. At the same time, the Folders are stored in a flat List that can be used for iterating and searching, etc. Next, the method which retrieves the folder list is cached and can be cached with the Organization’s ID in the cache-key so that each list can be kept separately. To make sure we don’t cache large lists of heavy Folder objects, I cached only lists of Folder IDs (not objects) and made the Folder objects also cached by their ID. Okay, then what about the updating part I talked about? The cached list of Folders would be invalidated whenever a Folder update occurred. It means that the paths would need to be re-computed again, but only once until the next update and that seemed reasonable enough. In the end, I didn’t use the closure table, although I’m keen on using it if the opportunity arises. One of the key things to coming up with the solution was knowing not only the data-structure but also knowing the nature of the data (how it’s used and how often does it tend to change). In all fairness, Bill Karwin also points out frequent updating as a weakness/pain-point of the Closure Table pattern. So there you have it my friends, use those clousure tables if you can, and if you can’t, then hopefully the approach I used here will give you some ideas!]]></summary></entry><entry><title type="html">Time Zone Conversion in MySQL</title><link href="https://ikewalker.github.io/flite-mechanics/blog/2016/04/11/time-zone-conversion-in-mysql/" rel="alternate" type="text/html" title="Time Zone Conversion in MySQL" /><published>2016-04-11T15:30:00+00:00</published><updated>2016-04-11T15:30:00+00:00</updated><id>https://ikewalker.github.io/flite-mechanics/blog/2016/04/11/time-zone-conversion-in-mysql</id><content type="html" xml:base="https://ikewalker.github.io/flite-mechanics/blog/2016/04/11/time-zone-conversion-in-mysql/"><![CDATA[<p>A wise man <a href="http://www.xaprb.com/blog/2014/01/30/timestamps-in-mysql/">once said</a> that the safest way to store date and time values in MySQL is to store Unix timestamps in an unsigned <code class="language-plaintext highlighter-rouge">INT</code> column. I agree with Baron, but most of us have to deal with <code class="language-plaintext highlighter-rouge">DATE</code>, <code class="language-plaintext highlighter-rouge">DATETIME</code>, and/or <code class="language-plaintext highlighter-rouge">TIMESTAMP</code> values sometimes, and it’s often useful to know how to convert values from one time zone to another.</p>

<p>##The CONVERT_TZ() Function</p>

<p>You can convert a temporal value in MySQL from one time zone to another using the <a href="http://dev.mysql.com/doc/refman/5.6/en/date-and-time-functions.html#function_convert-tz"><code class="language-plaintext highlighter-rouge">CONVERT_TZ()</code></a> function. As an example, say I want to watch the Boston Marathon in Melbourne, Australia but I’m afraid it may be on after my bed time. The elite men start at 10:25 AM on April 18, 2016. Boston’s time zone will be EDT that day, which is UTC-4. Melbourne’s time zone that day will be AEST, which is UTC+10. The following query will tell me what time the Boston Marathon starts in Melbourne:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; select convert_tz('2016-04-18 10:25','-04:00','+10:00');
+--------------------------------------------------+
| convert_tz('2016-04-18 10:25','-04:00','+10:00') |
+--------------------------------------------------+
| 2016-04-19 00:25:00                              |
+--------------------------------------------------+
1 row in set (0.00 sec)
</code></pre></div></div>

<p>Therefore the marathon starts are 12:25 AM on Tuesday in Melbourne, which is past my bedtime.</p>

<p>It may seem a little silly to use <code class="language-plaintext highlighter-rouge">CONVERT_TZ()</code> with offsets like that rather than just adding 14 hours like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; select '2016-04-18 10:25' + interval 14 hour;
+---------------------------------------+
| '2016-04-18 10:25' + interval 14 hour |
+---------------------------------------+
| 2016-04-19 00:25:00                   |
+---------------------------------------+
1 row in set (0.00 sec)
</code></pre></div></div>

<p>My argument for preferring <code class="language-plaintext highlighter-rouge">CONVERT_TZ()</code> is that it makes the query much more readable. In the first example it’s clear that I am converting a time from UTC-04:00 to UTC+10:00, whereas in the second example there’s no way to tell from the query why I’m adding 14 hours.</p>

<!-- more -->

<p>##Populating the Time Zone Tables</p>

<p>Using <code class="language-plaintext highlighter-rouge">CONVERT_TZ()</code> becomes more interesting using named time zones, but that only works if the system time zone tables are populated. For example if I try to find out what time the Boston Marathon starts in Melbourne using named time zones I may get a NULL value:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; select convert_tz('2016-04-18 10:25','America/New_York','Australia/Melbourne');
+-------------------------------------------------------------------------+
| convert_tz('2016-04-18 10:25','America/New_York','Australia/Melbourne') |
+-------------------------------------------------------------------------+
| NULL                                                                    |
+-------------------------------------------------------------------------+
1 row in set (0.00 sec)
</code></pre></div></div>

<p>Why did I get NULL? Probably because my time zone tables are empty. Those tables are not populated by default.</p>

<p>Following the instructions in the <a href="https://dev.mysql.com/doc/refman/5.6/en/time-zone-support.html#time-zone-installation">reference manual</a> I can populate those tables by running this command in the shell:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
</code></pre></div></div>

<p>Now I can use named time zones:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; select convert_tz('2016-04-18 10:25','America/New_York','Australia/Melbourne');
+-------------------------------------------------------------------------+
| convert_tz('2016-04-18 10:25','America/New_York','Australia/Melbourne') |
+-------------------------------------------------------------------------+
| 2016-04-19 00:25:00                                                     |
+-------------------------------------------------------------------------+
1 row in set (0.00 sec)
</code></pre></div></div>

<p>Bear in mind that time zone rules change sometimes, so you may want to review <a href="https://dev.mysql.com/doc/refman/5.6/en/time-zone-upgrades.html">the manual</a> and develop a plan to stay up to date with those changes.</p>

<p>##Know What Time Zone MySQL is Using</p>

<p>My examples so far have used static datetime values. When dealing with values stored in a table or returned by MySQL functions it’s important to know the context of those values if I want to convert them. For example I need to know what time zone MySQL is using if I want to convert a datetime value stored in a table, or the output of a function like <code class="language-plaintext highlighter-rouge">NOW()</code> to a different time zone.</p>

<p>The relevant session and global variables are named <code class="language-plaintext highlighter-rouge">time_zone</code> and <code class="language-plaintext highlighter-rouge">system_time_zone</code>. You can access them like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; select @@session.time_zone,@@global.time_zone,@@global.system_time_zone;
+---------------------+--------------------+---------------------------+
| @@session.time_zone | @@global.time_zone | @@global.system_time_zone |
+---------------------+--------------------+---------------------------+
| SYSTEM              | SYSTEM             | UTC                       |
+---------------------+--------------------+---------------------------+
1 row in set (0.00 sec)
</code></pre></div></div>

<p>If the <code class="language-plaintext highlighter-rouge">time_zone</code> variable is set to <code class="language-plaintext highlighter-rouge">SYSTEM</code> then the <code class="language-plaintext highlighter-rouge">system_time_zone</code> will be used.</p>]]></content><author><name>Ike Walker</name></author><category term="MySQL" /><summary type="html"><![CDATA[A wise man once said that the safest way to store date and time values in MySQL is to store Unix timestamps in an unsigned INT column. I agree with Baron, but most of us have to deal with DATE, DATETIME, and/or TIMESTAMP values sometimes, and it’s often useful to know how to convert values from one time zone to another. ##The CONVERT_TZ() Function You can convert a temporal value in MySQL from one time zone to another using the CONVERT_TZ() function. As an example, say I want to watch the Boston Marathon in Melbourne, Australia but I’m afraid it may be on after my bed time. The elite men start at 10:25 AM on April 18, 2016. Boston’s time zone will be EDT that day, which is UTC-4. Melbourne’s time zone that day will be AEST, which is UTC+10. The following query will tell me what time the Boston Marathon starts in Melbourne: mysql&gt; select convert_tz('2016-04-18 10:25','-04:00','+10:00'); +--------------------------------------------------+ | convert_tz('2016-04-18 10:25','-04:00','+10:00') | +--------------------------------------------------+ | 2016-04-19 00:25:00 | +--------------------------------------------------+ 1 row in set (0.00 sec) Therefore the marathon starts are 12:25 AM on Tuesday in Melbourne, which is past my bedtime. It may seem a little silly to use CONVERT_TZ() with offsets like that rather than just adding 14 hours like this: mysql&gt; select '2016-04-18 10:25' + interval 14 hour; +---------------------------------------+ | '2016-04-18 10:25' + interval 14 hour | +---------------------------------------+ | 2016-04-19 00:25:00 | +---------------------------------------+ 1 row in set (0.00 sec) My argument for preferring CONVERT_TZ() is that it makes the query much more readable. In the first example it’s clear that I am converting a time from UTC-04:00 to UTC+10:00, whereas in the second example there’s no way to tell from the query why I’m adding 14 hours. ##Populating the Time Zone Tables Using CONVERT_TZ() becomes more interesting using named time zones, but that only works if the system time zone tables are populated. For example if I try to find out what time the Boston Marathon starts in Melbourne using named time zones I may get a NULL value: mysql&gt; select convert_tz('2016-04-18 10:25','America/New_York','Australia/Melbourne'); +-------------------------------------------------------------------------+ | convert_tz('2016-04-18 10:25','America/New_York','Australia/Melbourne') | +-------------------------------------------------------------------------+ | NULL | +-------------------------------------------------------------------------+ 1 row in set (0.00 sec) Why did I get NULL? Probably because my time zone tables are empty. Those tables are not populated by default. Following the instructions in the reference manual I can populate those tables by running this command in the shell: mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql Now I can use named time zones: mysql&gt; select convert_tz('2016-04-18 10:25','America/New_York','Australia/Melbourne'); +-------------------------------------------------------------------------+ | convert_tz('2016-04-18 10:25','America/New_York','Australia/Melbourne') | +-------------------------------------------------------------------------+ | 2016-04-19 00:25:00 | +-------------------------------------------------------------------------+ 1 row in set (0.00 sec) Bear in mind that time zone rules change sometimes, so you may want to review the manual and develop a plan to stay up to date with those changes. ##Know What Time Zone MySQL is Using My examples so far have used static datetime values. When dealing with values stored in a table or returned by MySQL functions it’s important to know the context of those values if I want to convert them. For example I need to know what time zone MySQL is using if I want to convert a datetime value stored in a table, or the output of a function like NOW() to a different time zone. The relevant session and global variables are named time_zone and system_time_zone. You can access them like this: mysql&gt; select @@session.time_zone,@@global.time_zone,@@global.system_time_zone; +---------------------+--------------------+---------------------------+ | @@session.time_zone | @@global.time_zone | @@global.system_time_zone | +---------------------+--------------------+---------------------------+ | SYSTEM | SYSTEM | UTC | +---------------------+--------------------+---------------------------+ 1 row in set (0.00 sec) If the time_zone variable is set to SYSTEM then the system_time_zone will be used.]]></summary></entry><entry><title type="html">Announcing MySQL BRO Schema</title><link href="https://ikewalker.github.io/flite-mechanics/blog/2016/04/01/announcing-mysql-bro-schema/" rel="alternate" type="text/html" title="Announcing MySQL BRO Schema" /><published>2016-04-01T17:00:00+00:00</published><updated>2016-04-01T17:00:00+00:00</updated><id>https://ikewalker.github.io/flite-mechanics/blog/2016/04/01/announcing-mysql-bro-schema</id><content type="html" xml:base="https://ikewalker.github.io/flite-mechanics/blog/2016/04/01/announcing-mysql-bro-schema/"><![CDATA[<p>When MySQL released <a href="http://dev.mysql.com/doc/refman/5.6/en/performance-schema.html">performance_schema</a> it made a lot of new useful information available, but it wasn’t particularly user-friendly. The introduction of the MySQL <a href="https://github.com/mysql/mysql-sys">SYS schema</a> made it a lot easier for developers and DBAs to get useful insights from performance_schema. I was talking to some brogrammers the other day, and while they admitted that SYS schema is “totally legit”, they wished that it “spoke their language”.</p>

<p>To that end, I am happy to announce MySQL BRO schema. It’s a natural complement to SYS schema, and makes it easier for brogrammers to work with performance_schema.</p>

<p>Read on for a few examples of the BRO schema in action.</p>

<!--more-->

<p>The SYS schema has a view named <a href="https://dev.mysql.com/doc/refman/5.7/en/sys-user-summary.html"><code class="language-plaintext highlighter-rouge">user_summary</code></a>. In the BRO schema the equivalent is the <code class="language-plaintext highlighter-rouge">bro_summary</code> view.</p>

<p>Here’s a sample query on the <code class="language-plaintext highlighter-rouge">bro_summary</code> view:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; use bro
Database changed
mysql&gt; select * from bro_summary;
+------------------+-------------+--------------------+------------------------+-------------+-------------+-------------------+----------------------+--------------------+---------------+
| bro              | bro_queries | bro_query_brotency | bro_query_avg_brotency | tabro_scans | file_ibros  | file_ibro_latency | current_bronnections | total_bronnections | unique_brosts |
+------------------+-------------+--------------------+------------------------+-------------+-------------+-------------------+----------------------+--------------------+---------------+
| brobi_wan_kenobi |      230732 | 6.24 m             | 1.62 ms                |           0 |     3375788 | 1.83 h            |                    2 |                  2 |             1 |
| broto_baggins    |      173767 | 1.18 m             | 405.89 us              |         135 |    11841856 | 13.95 h           |                   11 |                 17 |             3 |
| hulk_brogan      |        1549 | 1.29 s             | 830.87 us              |         509 |        2720 | 732.04 ms         |                    5 |                516 |             2 |
| tony_bromo       |     1064933 | 28.68 m            | 1.62 ms                |         136 |    32476670 | 22.55 m           |                    5 |                 56 |             7 |
| brute            |       76009 | 1.00 h             | 47.55 ms               |       37188 |   254239005 | 2.44 h            |                    1 |              37461 |             2 |
+------------------+-------------+--------------------+------------------------+-------------+-------------+-------------------+----------------------+--------------------+---------------+
</code></pre></div></div>

<p>Likewise the <a href="https://dev.mysql.com/doc/refman/5.7/en/sys-host-summary.html"><code class="language-plaintext highlighter-rouge">host_summary</code></a> view from SYS schema is replaced with the <code class="language-plaintext highlighter-rouge">brost_summary</code> view in BRO schema:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql&gt; select * from brost_summary;
+--------------------------------+-------------+--------------------+------------------------+-------------+------------+-------------------+----------------------+--------------------+-------------+
| brost                          | bro_queries | bro_query_brotency | bro_query_avg_brotency | tabro_scans | file_ibros | file_ibro_latency | current_bronnections | total_bronnections | unique_bros |
+--------------------------------+-------------+--------------------+------------------------+-------------+------------+-------------------+----------------------+--------------------+-------------+
| background                     |        NULL | NULL               | NULL                   |        NULL |       NULL | NULL              |                    0 |                  2 |           0 |
| ip-10-60-41-26.ec2.internal    |       55959 | 3.52 h             | 226.20 ms              |       13368 |   52416910 | 3.66 h            |                   23 |                 22 |          15 |
| ip-10-102-56-230.ec2.internal  |     1792854 | 2.17 h             | 4.36 ms                |        6703 |   39771784 | 4.14 h            |                    1 |                  7 |           5 |
| ip-10-30-14-233.ec2.internal   |      860733 | 1.20 h             | 5.03 ms                |       46829 |   10609581 | 53.39 m           |                   14 |                  4 |           3 |
| ip-10-21-17-24.ec2.internal    |      552149 | 5.84 m             | 634.79 us              |       77911 |   39970438 | 16.93 m           |                   13 |                  6 |          18 |
| ip-10-199-205-174.ec2.internal |        3481 | 4.09 h             | 4.23 s                 |      187574 |   10722269 | 1.67 h            |                   12 |                  5 |          11 |
| ip-10-139-129-225.ec2.internal |     1909218 | 3.57 h             | 6.73 ms                |      117428 |    1310063 | 2.46 h            |                   21 |                 11 |          15 |
| ip-10-154-79-189.ec2.internal  |     1643780 | 2.72 h             | 5.95 ms                |      203993 |   31900386 | 3.44 h            |                   15 |                 12 |          14 |
| localbrost                     |      787149 | 3.10 h             | 14.17 ms               |      118728 |  894439407 | 4.28 d            |                   11 |              39514 |           3 |
+--------------------------------+-------------+--------------------+------------------------+-------------+------------+-------------------+----------------------+--------------------+-------------+
</code></pre></div></div>

<p>This just scratches the surface of the insights you can get from the BRO schema. The truly brolific amount of information in BRO schema makes it absolutely bronafide.</p>]]></content><author><name>Ike Walker</name></author><category term="MySQL" /><summary type="html"><![CDATA[When MySQL released performance_schema it made a lot of new useful information available, but it wasn’t particularly user-friendly. The introduction of the MySQL SYS schema made it a lot easier for developers and DBAs to get useful insights from performance_schema. I was talking to some brogrammers the other day, and while they admitted that SYS schema is “totally legit”, they wished that it “spoke their language”. To that end, I am happy to announce MySQL BRO schema. It’s a natural complement to SYS schema, and makes it easier for brogrammers to work with performance_schema. Read on for a few examples of the BRO schema in action.]]></summary></entry><entry><title type="html">Simplifying MySQL partition management using common_schema</title><link href="https://ikewalker.github.io/flite-mechanics/blog/2016/03/28/simplifying-mysql-partition-management-using-common-schema/" rel="alternate" type="text/html" title="Simplifying MySQL partition management using common_schema" /><published>2016-03-28T13:45:00+00:00</published><updated>2016-03-28T13:45:00+00:00</updated><id>https://ikewalker.github.io/flite-mechanics/blog/2016/03/28/simplifying-mysql-partition-management-using-common-schema</id><content type="html" xml:base="https://ikewalker.github.io/flite-mechanics/blog/2016/03/28/simplifying-mysql-partition-management-using-common-schema/"><![CDATA[<p>Maintaining partitioned tables in MySQL can be tedious. In a reporting database that uses monthly date partitions, I’ve found that I can simplify many frequent partition operations using the <a href="https://github.com/shlomi-noach/common_schema/releases">common_schema</a> <a href="https://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/query_script.html">QueryScript</a> language. Below I will describe two specific examples.</p>

<!--more-->

<p>#Removing old data</p>

<p>I like to keep a 2-3 month window of data in some of my reporting tables. In order to maintain that window I periodically purge data more than 3 months old using the <code class="language-plaintext highlighter-rouge">TRUNCATE PARTITION</code> command. Since I want to apply the same purge logic to all tables matching a consistent naming pattern, I can use the common_schema <a href="https://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/foreach.html"><code class="language-plaintext highlighter-rouge">foreach</code></a> command to iterate over the tables based on an <code class="language-plaintext highlighter-rouge">information_schema</code> query and run the same <code class="language-plaintext highlighter-rouge">TRUNCATE PARTITION</code> command on all of the tables:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>call common_schema.foreach(
  "select table_schema,table_name,partition_name 
    from information_schema.partitions 
    where table_schema = 'reporting' 
    and table_name like '%\_fact' 
    and table_rows != 0
    and partition_name &lt;= concat('p',date_format(current_date() - interval 3 month,'%Y%m'))",
  "alter table ${1}.${2} truncate partition ${3};");
</code></pre></div></div>

<p>#Adding new partitions</p>

<p>Recently I discovered that some of the partitioned tables in this reporting database did not have any partitions for 2016, so all of this year’s data ended up in the “pmax” partition. In order to fix this I used the common_schema <code class="language-plaintext highlighter-rouge">foreach</code> command to iterate over another <code class="language-plaintext highlighter-rouge">information_schema</code> query and run a <code class="language-plaintext highlighter-rouge">REORGANIZE PARTITION</code> command on all of the tables:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>call common_schema.foreach(
  "select p1.table_schema,p1.table_name,p1.partition_name
    from information_schema.PARTITIONS p1 
    where p1.PARTITION_NAME = 'pmax' 
    and p1.table_schema = 'reporting' 
    and p1.table_name like '%\_fact' 
    and exists (
      select NULL 
      from information_schema.partitions p2 
      where p2.table_Schema = p1.table_schema 
      and p2.table_name = p1.table_name 
      and p2.partition_name like 'p2015__'
    )
    and not exists (
      select NULL 
      from information_schema.partitions p2 
      where p2.table_Schema = p1.table_schema 
      and p2.table_name = p1.table_name 
      and p2.partition_name like 'p2016__'
    )",
    "ALTER TABLE ${1}.${2}
    REORGANIZE PARTITION ${3} INTO (
      PARTITION p201601 VALUES LESS THAN ('2016-02-01') ENGINE = InnoDB,
      PARTITION p201602 VALUES LESS THAN ('2016-03-01') ENGINE = InnoDB,
      PARTITION p201603 VALUES LESS THAN ('2016-04-01') ENGINE = InnoDB,
      PARTITION p201604 VALUES LESS THAN ('2016-05-01') ENGINE = InnoDB,
      PARTITION p201605 VALUES LESS THAN ('2016-06-01') ENGINE = InnoDB,
      PARTITION p201606 VALUES LESS THAN ('2016-07-01') ENGINE = InnoDB,
      PARTITION p201607 VALUES LESS THAN ('2016-08-01') ENGINE = InnoDB,
      PARTITION p201608 VALUES LESS THAN ('2016-09-01') ENGINE = InnoDB,
      PARTITION p201609 VALUES LESS THAN ('2016-10-01') ENGINE = InnoDB,
      PARTITION p201610 VALUES LESS THAN ('2016-11-01') ENGINE = InnoDB,
      PARTITION p201611 VALUES LESS THAN ('2016-12-01') ENGINE = InnoDB,
      PARTITION p201612 VALUES LESS THAN ('2017-01-01') ENGINE = InnoDB,
      PARTITION pmax VALUES LESS THAN (MAXVALUE) ENGINE = InnoDB
  );");
</code></pre></div></div>]]></content><author><name>Ike Walker</name></author><category term="MySQL" /><category term="common_schema" /><summary type="html"><![CDATA[Maintaining partitioned tables in MySQL can be tedious. In a reporting database that uses monthly date partitions, I’ve found that I can simplify many frequent partition operations using the common_schema QueryScript language. Below I will describe two specific examples.]]></summary></entry><entry><title type="html">Google Accelerated Mobile Pages and the &amp;amp;ltamp-ad&amp;amp;gt</title><link href="https://ikewalker.github.io/flite-mechanics/blog/2016/02/24/google-accelerated-mobile-pages-and-the/" rel="alternate" type="text/html" title="Google Accelerated Mobile Pages and the &amp;amp;ltamp-ad&amp;amp;gt" /><published>2016-02-24T14:07:00+00:00</published><updated>2016-02-24T14:07:00+00:00</updated><id>https://ikewalker.github.io/flite-mechanics/blog/2016/02/24/google-accelerated-mobile-pages-and-the</id><content type="html" xml:base="https://ikewalker.github.io/flite-mechanics/blog/2016/02/24/google-accelerated-mobile-pages-and-the/"><![CDATA[<p>Google’s new <a href="https://www.ampproject.org/">Accelerated Mobile Pages project</a> introduces a lot of new challenges for web page creation and design. It also presents some small challenges to Flite as an ad serving platform. The two ways in which the AMP project and its specfications interfere with our usual way of serving ads is through the restriction of written or external JavaScript and by serving ads through external iframes.</p>

<h2 id="no-javascript">No JavaScript</h2>
<p>Our platform, like many others, generally delivers its content to the page via a <code class="language-plaintext highlighter-rouge">&lt;script&gt;</code> tag. Google restricts the inclusion of <code class="language-plaintext highlighter-rouge">&lt;script&gt;</code> elements on AMP pages in the name of protecting the user experience and improving the loading efficiency of the pages. Instead, they require ads to be loaded via specially provisioned <code class="language-plaintext highlighter-rouge">&lt;amp-ad&gt;</code> elements over which the AMP runtime has control and whose contents can be loaded asynchronously based on that runtime’s logic. Accordingly, we had to develop a new way to deliver our ads to the page which fit in with this paradigm. On the face, this is very simple:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;amp-ad</span> <span class="na">width=</span><span class="s">320</span> <span class="na">height=</span><span class="s">568</span>
    <span class="na">type = </span><span class="s">"flite"</span>
    <span class="na">data-guid = </span><span class="s">"aa7bf589-6d51-4194-91f4-d22eef8e3688"</span>
    <span class="na">data-mixins=</span><span class="s">""</span><span class="nt">&gt;</span>
<span class="nt">&lt;/amp-ad&gt;</span>
</code></pre></div></div>
<p>This tag looks much different and simpler than our usual ad tags. It works in conjunction with the <a href="https://github.com/ampproject/amphtml/tree/master/ads">AMP Project’s ad library</a>. The AMP runtime’s <code class="language-plaintext highlighter-rouge">3p/integration.js</code> module loads in the <code class="language-plaintext highlighter-rouge">flite.js</code> module which then controls how our ads are served into AMP pages where an <code class="language-plaintext highlighter-rouge">&lt;amp-ad&gt;</code> tag is included.</p>

<!-- more -->

<h2 id="third-party-iframes">Third-Party Iframes</h2>
<p>A handful of our features and tracking functionality require access to the top level <code class="language-plaintext highlighter-rouge">window</code> in order to function. Most of our publisher customers serve our ads into iframes which are friendly, giving our ads full access to the top level <code class="language-plaintext highlighter-rouge">window</code>. For publishers who do not serve our ads into friendly iframes and instead into 3rd-party iframes – iframes with a different domain than the page – we institute the use of an <a href="http://stackoverflow.com/questions/1792842/iframe-buster-files-whats-their-purpose">iframe buster</a> in order to achieve this same access.</p>

<p>The AMP runtime and infrastructure load ads in through a 3rd party iframe. However, we cannot implement our usual iframe busting solution because publishers are not actually hosting their AMP pages. Instead, AMP pages are cached and served from <a href="https://www.ampproject.org/docs/get_started/about-amp.html#amp-cdn">Google’s own CDN</a>. This is a limitation currently facing all ad tech vendors which prevents popular executions like lightbox and adhesive footer ads.</p>

<p>Thankfully, Google provides <a href="https://www.ampproject.org/docs/guides/responsive/control_layout.html">built in layout tools</a> and APIs that give access to some <a href="https://github.com/ampproject/amphtml/tree/master/ads#the-iframe-sandbox">information from the top <code class="language-plaintext highlighter-rouge">window</code></a>. These tools allow us to retain our most important tracking and measurement functionality as well as allow for the development of new tools which take advantage of this information.</p>]]></content><author><name>Andy Hills</name></author><category term="Adtech" /><summary type="html"><![CDATA[Google’s new Accelerated Mobile Pages project introduces a lot of new challenges for web page creation and design. It also presents some small challenges to Flite as an ad serving platform. The two ways in which the AMP project and its specfications interfere with our usual way of serving ads is through the restriction of written or external JavaScript and by serving ads through external iframes. No JavaScript Our platform, like many others, generally delivers its content to the page via a &lt;script&gt; tag. Google restricts the inclusion of &lt;script&gt; elements on AMP pages in the name of protecting the user experience and improving the loading efficiency of the pages. Instead, they require ads to be loaded via specially provisioned &lt;amp-ad&gt; elements over which the AMP runtime has control and whose contents can be loaded asynchronously based on that runtime’s logic. Accordingly, we had to develop a new way to deliver our ads to the page which fit in with this paradigm. On the face, this is very simple: &lt;amp-ad width=320 height=568 type = "flite" data-guid = "aa7bf589-6d51-4194-91f4-d22eef8e3688" data-mixins=""&gt; &lt;/amp-ad&gt; This tag looks much different and simpler than our usual ad tags. It works in conjunction with the AMP Project’s ad library. The AMP runtime’s 3p/integration.js module loads in the flite.js module which then controls how our ads are served into AMP pages where an &lt;amp-ad&gt; tag is included. Third-Party Iframes A handful of our features and tracking functionality require access to the top level window in order to function. Most of our publisher customers serve our ads into iframes which are friendly, giving our ads full access to the top level window. For publishers who do not serve our ads into friendly iframes and instead into 3rd-party iframes – iframes with a different domain than the page – we institute the use of an iframe buster in order to achieve this same access. The AMP runtime and infrastructure load ads in through a 3rd party iframe. However, we cannot implement our usual iframe busting solution because publishers are not actually hosting their AMP pages. Instead, AMP pages are cached and served from Google’s own CDN. This is a limitation currently facing all ad tech vendors which prevents popular executions like lightbox and adhesive footer ads. Thankfully, Google provides built in layout tools and APIs that give access to some information from the top window. These tools allow us to retain our most important tracking and measurement functionality as well as allow for the development of new tools which take advantage of this information.]]></summary></entry></feed>