mrubyudf 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }