使用 Type_handler 向 MariaDB 添加新数据类型 – 第5部分

作者: Frédéric Descamps
原文链接:https://mariadb.org/adding-a-new-data-type-to-mariadb-with-type_handler-part-5/


我们正在使用 Type_handler 框架结束关于新数据类型的系列文章,但该框架尚未涵盖一些限制:

  • 无自定义索引方法。插件类型无法引入新的索引方法。
  • 无自定义哈希。插件类型无法提供自己的哈希函数用于基于哈希的操作。诸如 MEMORY 表索引、GROUP BY 和分区等操作会回退到底层类型的哈希。
  • 无新字段属性。插件类型无法定义除现有属性(长度、精度、小数位数和 GIS SRID)之外的自定义属性。

如果我们的 MONEY 数据类型能够定义例如要显示的货币,或者像这样的格式,那将会很方便:

  • $1,000,000
  • 40.000,50 EUR
  • ¥2 000 000

不幸的是,我们无法为数据类型创建新的字段属性。但你可能已经知道,列有一个名为 COMMENT 的额外属性。我们可以利用它来满足我们的需求。

扩展 Field_money

我们需要在 sql_type_money.h 中扩展我们的 Field_money 类,添加格式样式和显示配置结构:

  enum money_format_style
  {
    MONEY_FMT_CURRENCY_FIRST= 0,
    MONEY_FMT_CURRENCY_LAST
  };

```c
struct money_display_config
  {
    char currency[32];
    char decimal_sep;
    char thousands_sep;
    bool allow_grouping;
    money_format_style style;
  };
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>我们还添加了两个私有方法,用于解析 <code>COMMENT</code> 属性并格式化输出:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>private:
  bool parse_comment_config(money_display_config *cfg) const;
  void format_money_string(String *to, double nr,
              const money_display_config &cfg) const;
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>这两个函数将在 <code>plugin.cc</code> 中指定。</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>bool Field_money::parse_comment_config(money_display_config *cfg) const
{
  money_set_default_display_config(cfg);

```c
if (!comment.str || !comment.length)
    return true;

  const char *p= comment.str;
  const char *end= comment.str + comment.length;

  while (p < end)
  {
    const char *entry_end= (const char*) memchr(p, ';', (size_t) (end - p));
    if (!entry_end)
      entry_end= end;

    const char *eq= (const char*) memchr(p, '=', (size_t) (entry_end - p));
    if (eq)
    {
      const char *k= money_trim_left(p, eq);
      const char *k_end= money_trim_right_begin(k, eq);
      const char *v= money_trim_left(eq + 1, entry_end);
      const char *v_end= money_trim_right_begin(v, entry_end);
      size_t k_len= (size_t) (k_end - k);
      size_t v_len= (size_t) (v_end - v);
      money_apply_comment_option(k, k_len, v, v_len, cfg);
    }

    p= entry_end < end ? entry_end + 1 : end;
  }

  if (cfg->decimal_sep == cfg->thousands_sep)
  {
    if (cfg->decimal_sep == ',')
      cfg->thousands_sep= '.';
    else
      cfg->thousands_sep= ',';
  }

  return true;
}

void Field_money::format_money_string(String *to, double nr,
                                      const money_display_config &cfg) const
{
  decimal_digits_t out_dec= money_effective_decimals(dec);
  char raw[DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE];
  size_t raw_len= my_fcvt(nr, out_dec, raw, NULL);
  raw[raw_len]= '\0';
  const char *p= raw;
  bool negative= false;

  if (*p == '-')
  {
    negative= true;
    p++;
  }

  const char *dot= strchr(p, '.');
  size_t int_len= dot ? (size_t) (dot - p) : strlen(p);
  const char *frac= dot ? dot + 1 : "";
  size_t frac_len= dot ? strlen(frac) : 0;

  to->length(0);
  to->set_charset(system_charset_info);

  if (negative && to->append('-'))
    return;

  if (cfg.style == MONEY_FMT_CURRENCY_FIRST && money_append_currency(to, cfg, true))
    return;

  for (size_t i= 0; i < int_len; i++)
  {
    if (cfg.allow_grouping && cfg.thousands_sep && i > 0 && ((int_len - i) % 3) == 0)
    {
      if (to->append(cfg.thousands_sep))
        return;
    }
    if (to->append(p[i]))
      return;
  }

  if (out_dec > 0)
  {
    if (to->append(cfg.decimal_sep))
      return;
    for (uint i= 0; i < out_dec; i++)
    {
      if (to->append((i < frac_len) ? frac[i] : '0'))
        return;
    }
  }

  if (cfg.style == MONEY_FMT_CURRENCY_LAST && money_append_currency(to, cfg, false))
    return;
}

这两个函数所使用的其他所有函数都定义在一个新的包含文件 money.h 中。

最后,我们修改了 Field_money::send(Protocol *protocol),使其在显示之前解析配置。

bool Field_money::send(Protocol *protocol)
{
  DBUG_ASSERT(marked_for_read());

```cpp
String money_buf;

  money_display_config cfg;
  parse_comment_config(&cfg);
  format_money_string(&money_buf, Field_money::val_real(), cfg);

  return protocol->store(&money_buf);
}
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>修改后的数据类型代码一如既往地存放在 <a href="https://github.com/lefred/type_money">GitHub 仓库</a> 的 <a href="https://github.com/lefred/type_money/tree/part5">part5 分支</a> 中。</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">示例</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>俗话说“一图胜千言”,让我们看看这个插件的实际效果:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>MariaDB [test]> install soname 'type_money';
Query OK, 0 rows affected (0.012 sec

```sql
MariaDB [test]> use test;
Database changed

MariaDB [test]> create table t1 (id int auto_increment primary key, 
    amount money(10,2) comment
      'currency=EUR;format=currenty_last;decimal=,;thousands=space',
    credit money(10,2) default null comment
     'currency=$;format=currency_first;decimal=.;thousands=,'
    ) engine=innodb;
Query OK, 0 rows affected (0.006 sec)

MariaDB [test]> show create table t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `amount` money(10,2) DEFAULT NULL COMMENT 'currency=EUR;format=currenty_last;decimal=,;thousands=space',
  `credit` money(10,2) DEFAULT NULL COMMENT 'currency=$;format=currency_first;decimal=.;thousands=,',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci
1 row in set (0.011 sec)

MariaDB [test]> insert into t1 (amount,credit) values (41578.4,2342);
Query OK, 1 row affected (0.011 sec)

MariaDB [test]> insert into t1 (amount,credit) values (24.4,12.50);
Query OK, 1 row affected (0.013 sec)

MariaDB [test]> insert into t1 (amount,credit) values (15678.309,-1234.299);
Query OK, 1 row affected (0.006 sec)

MariaDB [test]> select * from t1;
+----+---------------+------------+
| id | amount        | credit     |
+----+---------------+------------+
|  1 | 41 578,40 EUR | $2,342.00  |
|  2 | 15 678,31 EUR | -$1,234.30 |
|  3 | 24,40 EUR     | $12.50     |
+----+---------------+------------+
3 rows in set (0.010 sec)

结论

正如您在本系列中所见,使用 Type_handler 框架向 MariaDB 添加新数据类型非常简单。

与 MySQL 相比,添加新数据类型要容易得多,而且 MariaDB 中提供的新类型(如 INET4INET6UUID)都已经使用了该框架。

那么,请告诉我们您正在添加哪种数据类型,祝编码愉快!