mrubyudf 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # mrubyudf
2
+
3
+ これは mruby で MySQL のユーザー定義関数(UDF)を簡単に作成するためのツールです。
4
+
5
+ ## インストール
6
+
7
+ ```sh
8
+ % gem install mrubyudf
9
+ ```
10
+
11
+ ## 準備
12
+
13
+ mysql_config コマンドと MySQL のヘッダファイルが必要です。
14
+
15
+ mysql-8.0.20-linux-glibc2.12-x86_64.tar.xz みたいなファイルを展開した中には入ってるはずです。
16
+
17
+ それ以外の場合は MySQL 開発環境のインストールが必要になるかも知れません。
18
+ Ubuntu の場合は libmysqlclient-dev パッケージをインストールすればいいと思います。
19
+
20
+ ## 使い方
21
+
22
+
23
+ まず mruby をインストールする必要があります。mruby を動的リンクにするためのパッチをあてて make します。
24
+
25
+ ```sh
26
+ % git clone git@github.com:mruby/mruby.git
27
+ % cd mruby
28
+ % patch -p1 < $GEM_HOME/gems/mrubyudf-0.*/misc/mruby-shared.patch
29
+ % make
30
+ ```
31
+
32
+ うまくいかない場合は頑張ってください。
33
+
34
+ この mruby ディレクトリを MRUBY_PATH 環境変数に設定しておきます。
35
+
36
+ ```sh
37
+ % export MRUBY_PATH=/path/to/mruby
38
+ ```
39
+
40
+ 関数本体を作ります。ここではフィボナッチ数を返す fib() 関数を fib.rb ファイルとして作ります。
41
+
42
+ ```ruby
43
+ LONG_LONG_MAX = 9223372036854775807
44
+
45
+ def fib(n)
46
+ b = 1
47
+ c = 0
48
+ n.times do
49
+ a, b = b, c
50
+ c = a + b
51
+ raise 'Overflow' if c > LONG_LONG_MAX
52
+ end
53
+ c
54
+ end
55
+ ```
56
+
57
+ mruby で実行して動きを確かめます。
58
+
59
+ ```sh
60
+ % $MRUBY_PATH/bin/mruby -r ./fib.rb -e 'p fib(10)'
61
+ 55
62
+ % $MRUBY_PATH/bin/mruby -r ./fib.rb -e 'p fib(92)'
63
+ 7540113804746346429
64
+ % $MRUBY_PATH/bin/mruby -r ./fib.rb -e 'p fib(93)'
65
+ trace (most recent call last):
66
+ [2] -e:1
67
+ [1] -e:6:in fib
68
+ -e:9:in fib: Overflow (RuntimeError)
69
+ ```
70
+
71
+ 関数名、戻り値の型、引数の型等の情報を fib.spec ファイルで次のような感じで作ります。
72
+
73
+ ```ruby
74
+ MrubyUdf.function do |f|
75
+ f.name = 'fib' # 関数名は fib
76
+ f.return_type = Integer # 戻り値は Integer
77
+ f.arguments = [ # 引数は一つで型は Integer
78
+ Integer
79
+ ]
80
+ end
81
+ ```
82
+
83
+ コンパイル。
84
+
85
+ ```sh
86
+ % mrubyudf fib.spec
87
+ ```
88
+
89
+ うまくいけば fib.so ファイルができます。
90
+
91
+ これを MySQL のプラグインディレクトリにコピーします。
92
+
93
+ ```sh
94
+ % mysql_config --plugindir
95
+ /usr/local/mysql/lib/plugin
96
+ % sudo cp fib.so /usr/local/mysql/lib/plugin/
97
+ ```
98
+
99
+ MySQL に組み込みます。一度やっておけば mysqld を再起動しても自動的に組み込まれます。
100
+
101
+ ```sql
102
+ % mysql -uroot
103
+ mysql> create function fib returns int soname 'fib.so';
104
+ ```
105
+
106
+ 使ってみます。
107
+
108
+ ```sql
109
+ mysql> select fib(10);
110
+ +---------+
111
+ | fib(10) |
112
+ +---------+
113
+ | 55 |
114
+ +---------+
115
+ 1 row in set (0.04 sec)
116
+
117
+ mysql> select fib(92);
118
+ +---------------------+
119
+ | fib(92) |
120
+ +---------------------+
121
+ | 7540113804746346429 |
122
+ +---------------------+
123
+ 1 row in set (0.04 sec)
124
+
125
+ mysql> select fib(93);
126
+ +---------+
127
+ | fib(93) |
128
+ +---------+
129
+ | NULL |
130
+ +---------+
131
+ 1 row in set (0.04 sec)
132
+ ```
133
+
134
+ 関数が要らなくなったら破棄します。
135
+
136
+ ```sql
137
+ mysql> drop function fib;
138
+ ```
139
+
140
+ drop しないで so ファイルを更新したりすると、mysqld が落ちるので注意。
141
+
142
+ example 配下にテキトーなサンプルがいくつかあります。
143
+
144
+
145
+ ## ライセンス
146
+
147
+ このツール自体は GPL 3 です。
148
+
149
+ このツールによって作られた so ファイルは、このツールのライセンスとは無関係です。
150
+
151
+ ## 作者
152
+
153
+ とみたまさひろ <https://twitter.com/tmtms>
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "mrubyudf"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3 @@
1
+ def circumference(r)
2
+ 2 * Math::PI * r
3
+ end
@@ -0,0 +1,8 @@
1
+ #!ruby
2
+ MrubyUdf.function do |f|
3
+ f.name = 'circumference'
4
+ f.return_type = Float
5
+ f.arguments = [
6
+ Float
7
+ ]
8
+ end
data/example/fib.rb ADDED
@@ -0,0 +1,12 @@
1
+ LONG_LONG_MAX = 9223372036854775807
2
+
3
+ def fib(n)
4
+ b = 1
5
+ c = 0
6
+ n.times do
7
+ a, b = b, c
8
+ c = a + b
9
+ raise 'Overflow' if c > LONG_LONG_MAX
10
+ end
11
+ c
12
+ end
data/example/fib.spec ADDED
@@ -0,0 +1,8 @@
1
+ #!ruby
2
+ MrubyUdf.function do |f|
3
+ f.name = 'fib'
4
+ f.return_type = Integer
5
+ f.arguments = [
6
+ Integer
7
+ ]
8
+ end
@@ -0,0 +1,3 @@
1
+ def repeat_str(str, n)
2
+ str * n
3
+ end
@@ -0,0 +1,9 @@
1
+ #!ruby
2
+ MrubyUdf.function do |f|
3
+ f.name = 'repeat_str'
4
+ f.return_type = String
5
+ f.arguments = [
6
+ String,
7
+ Integer,
8
+ ]
9
+ end
data/example/rownum.rb ADDED
@@ -0,0 +1,4 @@
1
+ $n = 0
2
+ def rownum()
3
+ $n += 1
4
+ end
@@ -0,0 +1,6 @@
1
+ #!ruby
2
+ MrubyUdf.function do |f|
3
+ f.name = 'rownum'
4
+ f.return_type = Integer
5
+ f.arguments = []
6
+ end
@@ -0,0 +1,3 @@
1
+ def swapcase(s)
2
+ s.swapcase
3
+ end
@@ -0,0 +1,8 @@
1
+ #!ruby
2
+ MrubyUdf.function do |f|
3
+ f.name = 'swapcase'
4
+ f.return_type = String
5
+ f.arguments = [
6
+ String
7
+ ]
8
+ end
data/exe/mrubyudf ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/ruby
2
+ require 'mrubyudf'
3
+
4
+ MrubyUdf::Command.run
data/lib/mrubyudf.rb ADDED
@@ -0,0 +1,15 @@
1
+ require_relative 'mrubyudf/version'
2
+ require_relative 'mrubyudf/function'
3
+ require_relative 'mrubyudf/template'
4
+ require_relative 'mrubyudf/command'
5
+
6
+ class MrubyUdf
7
+ class << self
8
+ def function(&block)
9
+ return @function unless block
10
+ f = MrubyUdf::Function.new
11
+ block.call f
12
+ @function = f
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,53 @@
1
+ class MrubyUdf
2
+ class Command
3
+ def self.run
4
+ self.new.run
5
+ end
6
+
7
+ def run
8
+ parse_args(ARGV)
9
+ load @spec
10
+ Dir.chdir(File.dirname(@spec)) do
11
+ compile
12
+ end
13
+ end
14
+
15
+ def parse_args(args)
16
+ raise "Usage: #$0 foo.spec" if args.size != 1
17
+ @spec = args.first
18
+ end
19
+
20
+ def compile
21
+ f = MrubyUdf.function
22
+ c = MrubyUdf::Template.new(f).result
23
+ File.write("#{f.name}_udf.c", c)
24
+ mruby_path = ENV['MRUBY_PATH'] || search_mruby_path
25
+ mrbc = [mruby_path, 'bin/mrbc'].join('/')
26
+ cc = ENV['CC'] || 'gcc'
27
+ cflags = ENV['CFLAGS'] || '-shared -fPIC'
28
+ cflags += " -I #{mruby_path}/include #{mysql_include}"
29
+ mruby_libpath = "#{mruby_path}/build/host/lib"
30
+ libflags = "-L#{mruby_libpath} -Wl,--rpath,#{mruby_libpath}"
31
+ exec_command "#{mrbc} -B #{f.name}_mrb #{f.name}.rb"
32
+ exec_command "#{cc} #{cflags} #{f.name}_udf.c #{f.name}.c #{libflags} -lmruby -lm -o #{f.name}.so"
33
+ end
34
+
35
+ def exec_command(str)
36
+ puts str
37
+ stdout = %x(#{str})
38
+ raise 'failed' unless $?.success?
39
+ stdout.chomp
40
+ end
41
+
42
+ def search_mruby_path
43
+ ENV['PATH'].split(/:/).each do |path|
44
+ return File.dirname(path) if File.executable? "#{path}/mrbc"
45
+ end
46
+ raise "command not found: mrbc"
47
+ end
48
+
49
+ def mysql_include
50
+ exec_command 'mysql_config --include'
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,70 @@
1
+ class MrubyUdf
2
+ class Function
3
+ attr_accessor :name
4
+ attr_accessor :return_type
5
+ attr_accessor :arguments
6
+
7
+ def initialize
8
+ @name = nil
9
+ @return_type = Integer
10
+ @arguments = []
11
+ end
12
+
13
+ def mysql_type(arg)
14
+ case
15
+ when arg == Integer
16
+ "INT_RESULT"
17
+ when arg == Float
18
+ "REAL_RESULT"
19
+ when arg == String
20
+ "STRING_RESULT"
21
+ else
22
+ raise "invalid type: #{arg}"
23
+ end
24
+ end
25
+
26
+ def arg_mysql_to_mruby
27
+ arguments.map.with_index do |arg, i|
28
+ case
29
+ when arg == Integer
30
+ "mrb_fixnum_value(*((long long *)args->args[#{i}]))"
31
+ when arg == Float
32
+ "mrb_float_value(mrb, *((double *)args->args[#{i}]))"
33
+ when arg == String
34
+ "mrb_str_new_cstr(mrb, (char *)args->args[#{i}])"
35
+ end
36
+ end.join(", ")
37
+ end
38
+
39
+ def return_ctype
40
+ case
41
+ when return_type == Integer
42
+ "long long"
43
+ when return_type == Float
44
+ "double"
45
+ when return_type == String
46
+ "char *"
47
+ else
48
+ raise "invalid return type: #{return_type}"
49
+ end
50
+ end
51
+
52
+
53
+ def return_mruby_to_mysql
54
+ case
55
+ when return_type == Integer
56
+ "mrb_fixnum(ret)"
57
+ when return_type == Float
58
+ "mrb_float(ret)"
59
+ when return_type == String
60
+ "RSTRING_PTR(ret)"
61
+ else
62
+ raise "invalid return type: #{return_type}"
63
+ end
64
+ end
65
+
66
+ def context
67
+ binding
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,70 @@
1
+ #include <stdio.h>
2
+ #include <string.h>
3
+ #include "mruby.h"
4
+ #include "mruby/string.h"
5
+ #include "mruby/irep.h"
6
+ #include "mysql.h"
7
+
8
+ extern const uint8_t <%= name %>_mrb[];
9
+
10
+ bool <%= name %>_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
11
+ {
12
+ if (args->arg_count != <%= arguments.size %>) {
13
+ strcpy(message, "<%= name %>() requires <%= arguments.size %> argument(s)");
14
+ return true;
15
+ }
16
+ <% arguments.each_with_index do |arg, i| %>
17
+ args->arg_type[<%= i %>] = <%= mysql_type arg %>;
18
+ <% end %>
19
+ mrb_state *mrb = mrb_open();
20
+ if (!mrb) {
21
+ strcpy(message, "mrb_open() error");
22
+ return true;
23
+ }
24
+ initid->ptr = (void *)mrb;
25
+ mrb_load_irep(mrb, <%= name %>_mrb);
26
+ if (mrb->exc) {
27
+ mrb_value s = mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0);
28
+ const char *cs = mrb_string_value_cstr(mrb, &s);
29
+ strncpy(message, cs, MYSQL_ERRMSG_SIZE);
30
+ fprintf(stderr, "%s\n", cs);
31
+ return true;
32
+ }
33
+ return false;
34
+ }
35
+
36
+ void <%= name %>_deinit(UDF_INIT *initid)
37
+ {
38
+ mrb_state *mrb = (mrb_state *)initid->ptr;
39
+ if (mrb) {
40
+ mrb_close(mrb);
41
+ }
42
+ }
43
+
44
+ <%= return_ctype %> <%= name %>(UDF_INIT *initid, UDF_ARGS *args,
45
+ <% if return_type == String %>
46
+ char *result, unsigned long *length,
47
+ <% end %>
48
+ char *is_null, char *error)
49
+ {
50
+ mrb_state *mrb = (mrb_state *)initid->ptr;
51
+ mrb_value ret = mrb_funcall(mrb, mrb_top_self(mrb), "<%= name %>", <%= arguments.size %>
52
+ <% if arguments.size > 0 %>
53
+ , <%= arg_mysql_to_mruby %>
54
+ <% end %>
55
+ );
56
+ if (mrb->exc) {
57
+ mrb_value s = mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0);
58
+ fprintf(stderr, "<%= name %>: %s\n", mrb_string_value_cstr(mrb, &s));
59
+ *is_null = true;
60
+ return 0;
61
+ }
62
+ if (mrb_nil_p(ret)) {
63
+ *is_null = true;
64
+ return 0;
65
+ }
66
+ <% if return_type == String %>
67
+ *length = RSTRING_LEN(ret);
68
+ <% end %>
69
+ return <%= return_mruby_to_mysql %>;
70
+ }