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