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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/Gemfile +6 -0
- data/LICENSE +674 -0
- data/README.md +153 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/example/circumference.rb +3 -0
- data/example/circumference.spec +8 -0
- data/example/fib.rb +12 -0
- data/example/fib.spec +8 -0
- data/example/repeat_str.rb +3 -0
- data/example/repeat_str.spec +9 -0
- data/example/rownum.rb +4 -0
- data/example/rownum.spec +6 -0
- data/example/swapcase.rb +3 -0
- data/example/swapcase.spec +8 -0
- data/exe/mrubyudf +4 -0
- data/lib/mrubyudf.rb +15 -0
- data/lib/mrubyudf/command.rb +53 -0
- data/lib/mrubyudf/function.rb +70 -0
- data/lib/mrubyudf/template.erb +70 -0
- data/lib/mrubyudf/template.rb +16 -0
- data/lib/mrubyudf/version.rb +3 -0
- data/misc/mruby-shared.patch +40 -0
- data/mrubyudf.gemspec +25 -0
- metadata +71 -0
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
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
data/example/fib.rb
ADDED
data/example/fib.spec
ADDED
data/example/rownum.rb
ADDED
data/example/rownum.spec
ADDED
data/example/swapcase.rb
ADDED
data/exe/mrubyudf
ADDED
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
|
+
}
|