linkage 0.0.8 → 0.1.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.yardopts +1 -0
- data/Gemfile +1 -19
- data/Gemfile-java +3 -0
- data/README.markdown +88 -34
- data/Rakefile +16 -15
- data/TODO +4 -0
- data/lib/linkage/comparator.rb +139 -144
- data/lib/linkage/comparators/compare.rb +236 -29
- data/lib/linkage/comparators/strcompare.rb +85 -0
- data/lib/linkage/comparators/within.rb +24 -20
- data/lib/linkage/configuration.rb +44 -466
- data/lib/linkage/dataset.rb +28 -127
- data/lib/linkage/exceptions.rb +5 -0
- data/lib/linkage/field.rb +6 -37
- data/lib/linkage/field_set.rb +3 -3
- data/lib/linkage/match_recorder.rb +22 -0
- data/lib/linkage/match_set.rb +34 -0
- data/lib/linkage/match_sets/csv.rb +39 -0
- data/lib/linkage/match_sets/database.rb +45 -0
- data/lib/linkage/matcher.rb +30 -0
- data/lib/linkage/result_set.rb +25 -110
- data/lib/linkage/result_sets/csv.rb +54 -0
- data/lib/linkage/result_sets/database.rb +42 -0
- data/lib/linkage/runner.rb +57 -16
- data/lib/linkage/score_recorder.rb +30 -0
- data/lib/linkage/score_set.rb +49 -0
- data/lib/linkage/score_sets/csv.rb +64 -0
- data/lib/linkage/score_sets/database.rb +77 -0
- data/lib/linkage/version.rb +1 -1
- data/lib/linkage.rb +14 -17
- data/linkage.gemspec +13 -1
- data/linkage.gemspec-java +32 -0
- data/test/helper.rb +30 -23
- data/test/integration/test_cross_linkage.rb +46 -25
- data/test/integration/test_database_result_set.rb +55 -0
- data/test/integration/test_dual_linkage.rb +19 -94
- data/test/integration/test_self_linkage.rb +100 -203
- data/test/integration/test_within_comparator.rb +24 -77
- data/test/unit/comparators/test_compare.rb +254 -50
- data/test/unit/comparators/test_strcompare.rb +45 -0
- data/test/unit/comparators/test_within.rb +14 -26
- data/test/unit/match_sets/test_csv.rb +78 -0
- data/test/unit/match_sets/test_database.rb +63 -0
- data/test/unit/result_sets/test_csv.rb +111 -0
- data/test/unit/result_sets/test_database.rb +68 -0
- data/test/unit/score_sets/test_csv.rb +151 -0
- data/test/unit/score_sets/test_database.rb +149 -0
- data/test/unit/test_comparator.rb +46 -83
- data/test/unit/test_comparators.rb +4 -0
- data/test/unit/test_configuration.rb +99 -145
- data/test/unit/test_dataset.rb +52 -73
- data/test/unit/test_field.rb +4 -55
- data/test/unit/test_field_set.rb +6 -6
- data/test/unit/test_match_recorder.rb +23 -0
- data/test/unit/test_match_set.rb +23 -0
- data/test/unit/test_match_sets.rb +4 -0
- data/test/unit/test_matcher.rb +44 -0
- data/test/unit/test_result_set.rb +24 -223
- data/test/unit/test_result_sets.rb +4 -0
- data/test/unit/test_runner.rb +122 -17
- data/test/unit/test_runners.rb +4 -0
- data/test/unit/test_score_recorder.rb +25 -0
- data/test/unit/test_score_set.rb +37 -0
- data/test/unit/test_score_sets.rb +4 -0
- metadata +183 -90
- data/Gemfile.lock +0 -92
- data/lib/linkage/comparators/binary.rb +0 -12
- data/lib/linkage/data.rb +0 -175
- data/lib/linkage/decollation.rb +0 -93
- data/lib/linkage/expectation.rb +0 -21
- data/lib/linkage/expectations/exhaustive.rb +0 -63
- data/lib/linkage/expectations/simple.rb +0 -168
- data/lib/linkage/function.rb +0 -148
- data/lib/linkage/functions/binary.rb +0 -30
- data/lib/linkage/functions/cast.rb +0 -54
- data/lib/linkage/functions/length.rb +0 -29
- data/lib/linkage/functions/strftime.rb +0 -33
- data/lib/linkage/functions/trim.rb +0 -30
- data/lib/linkage/group.rb +0 -55
- data/lib/linkage/meta_object.rb +0 -139
- data/lib/linkage/runner/single_threaded.rb +0 -187
- data/lib/linkage/utils.rb +0 -164
- data/lib/linkage/warnings.rb +0 -5
- data/test/integration/test_collation.rb +0 -45
- data/test/integration/test_configuration.rb +0 -268
- data/test/integration/test_dataset.rb +0 -116
- data/test/integration/test_functions.rb +0 -88
- data/test/integration/test_result_set.rb +0 -85
- data/test/integration/test_scoring.rb +0 -84
- data/test/unit/expectations/test_exhaustive.rb +0 -111
- data/test/unit/expectations/test_simple.rb +0 -303
- data/test/unit/functions/test_binary.rb +0 -54
- data/test/unit/functions/test_cast.rb +0 -98
- data/test/unit/functions/test_length.rb +0 -52
- data/test/unit/functions/test_strftime.rb +0 -60
- data/test/unit/functions/test_trim.rb +0 -43
- data/test/unit/runner/test_single_threaded.rb +0 -12
- data/test/unit/test_data.rb +0 -445
- data/test/unit/test_decollation.rb +0 -201
- data/test/unit/test_function.rb +0 -233
- data/test/unit/test_group.rb +0 -38
- data/test/unit/test_meta_object.rb +0 -208
- data/test/unit/test_utils.rb +0 -341
data/lib/linkage/decollation.rb
DELETED
@@ -1,93 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module Linkage
|
3
|
-
module Decollation
|
4
|
-
def decollate(string, database_type, collation)
|
5
|
-
case database_type
|
6
|
-
when :mysql
|
7
|
-
decollate_mysql(string, collation)
|
8
|
-
else
|
9
|
-
string
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def decollate_mysql(string, collation)
|
14
|
-
case collation
|
15
|
-
when "latin1_swedish_ci"
|
16
|
-
decollate_mysql_latin1_swedish_ci(string)
|
17
|
-
else
|
18
|
-
string
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def decollate_mysql_latin1_swedish_ci(string)
|
23
|
-
result = string.strip
|
24
|
-
result.each_char.with_index do |char, i|
|
25
|
-
case char
|
26
|
-
when 'A', 'a', 'À', 'Á', 'Â', 'Ã', 'à', 'á', 'â', 'ã'
|
27
|
-
result[i] = 'A'
|
28
|
-
when 'B', 'b'
|
29
|
-
result[i] = 'B'
|
30
|
-
when 'C', 'c', 'Ç', 'ç'
|
31
|
-
result[i] = 'C'
|
32
|
-
when 'D', 'd', 'Ð', 'ð'
|
33
|
-
result[i] = 'D'
|
34
|
-
when 'E', 'e', 'È', 'É', 'Ê', 'Ë', 'è', 'é', 'ê', 'ë'
|
35
|
-
result[i] = 'E'
|
36
|
-
when 'F', 'f'
|
37
|
-
result[i] = 'F'
|
38
|
-
when 'G', 'g'
|
39
|
-
result[i] = 'G'
|
40
|
-
when 'H', 'h'
|
41
|
-
result[i] = 'H'
|
42
|
-
when 'I', 'i', 'Ì', 'Í', 'Î', 'Ï', 'ì', 'í', 'î', 'ï'
|
43
|
-
result[i] = 'I'
|
44
|
-
when 'J', 'j'
|
45
|
-
result[i] = 'J'
|
46
|
-
when 'K', 'k'
|
47
|
-
result[i] = 'K'
|
48
|
-
when 'L', 'l'
|
49
|
-
result[i] = 'L'
|
50
|
-
when 'M', 'm'
|
51
|
-
result[i] = 'M'
|
52
|
-
when 'N', 'n', 'Ñ', 'ñ'
|
53
|
-
result[i] = 'N'
|
54
|
-
when 'O', 'o', 'Ò', 'Ó', 'Ô', 'Õ', 'ò', 'ó', 'ô', 'õ'
|
55
|
-
result[i] = 'O'
|
56
|
-
when 'P', 'p'
|
57
|
-
result[i] = 'P'
|
58
|
-
when 'Q', 'q'
|
59
|
-
result[i] = 'Q'
|
60
|
-
when 'R', 'r'
|
61
|
-
result[i] = 'R'
|
62
|
-
when 'S', 's'
|
63
|
-
result[i] = 'S'
|
64
|
-
when 'T', 't'
|
65
|
-
result[i] = 'T'
|
66
|
-
when 'U', 'u', 'Ù', 'Ú', 'Û', 'ù', 'ú', 'û'
|
67
|
-
result[i] = 'U'
|
68
|
-
when 'V', 'v'
|
69
|
-
result[i] = 'V'
|
70
|
-
when 'W', 'w'
|
71
|
-
result[i] = 'W'
|
72
|
-
when 'X', 'x'
|
73
|
-
result[i] = 'X'
|
74
|
-
when 'Y', 'y', 'Ü', 'Ý', 'ü', 'ý'
|
75
|
-
result[i] = 'Y'
|
76
|
-
when 'Z', 'z'
|
77
|
-
result[i] = 'Z'
|
78
|
-
when '[', 'Å', 'å'
|
79
|
-
result[i] = '['
|
80
|
-
when '\\', 'Ä', 'Æ', 'ä', 'æ'
|
81
|
-
result[i] = '\\'
|
82
|
-
when ']', 'Ö', 'ö'
|
83
|
-
result[i] = ']'
|
84
|
-
when 'Ø', 'ø'
|
85
|
-
result[i] = 'Ø'
|
86
|
-
when 'Þ', 'þ'
|
87
|
-
result[i] = 'Þ'
|
88
|
-
end
|
89
|
-
end
|
90
|
-
result
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
data/lib/linkage/expectation.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
module Linkage
|
2
|
-
# The Expectation class contains information about how two datasets
|
3
|
-
# should be linked.
|
4
|
-
class Expectation
|
5
|
-
def kind
|
6
|
-
raise NotImplementedError
|
7
|
-
end
|
8
|
-
|
9
|
-
def apply_to(*args)
|
10
|
-
raise NotImplementedError
|
11
|
-
end
|
12
|
-
|
13
|
-
def decollation_needed?
|
14
|
-
false
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), "expectations", "*.rb"))).each do |filename|
|
20
|
-
require filename
|
21
|
-
end
|
@@ -1,63 +0,0 @@
|
|
1
|
-
module Linkage
|
2
|
-
module Expectations
|
3
|
-
class Exhaustive < Expectation
|
4
|
-
attr_reader :comparator, :threshold, :mode
|
5
|
-
|
6
|
-
def initialize(comparator, threshold, mode)
|
7
|
-
@comparator = comparator
|
8
|
-
@threshold = threshold
|
9
|
-
@mode = mode
|
10
|
-
end
|
11
|
-
|
12
|
-
def kind
|
13
|
-
if @kind.nil?
|
14
|
-
if @comparator.lhs_args.length != @comparator.rhs_args.length
|
15
|
-
@kind = :cross
|
16
|
-
else
|
17
|
-
@kind = :self
|
18
|
-
@comparator.lhs_args.each_with_index do |lhs_arg, index|
|
19
|
-
rhs_arg = @comparator.rhs_args[index]
|
20
|
-
if !lhs_arg.objects_equal?(rhs_arg)
|
21
|
-
@kind = :cross
|
22
|
-
break
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# Check for dual-linkage.
|
28
|
-
if @kind == :cross
|
29
|
-
# Assume that all lhs arguments have the same dataset, as well
|
30
|
-
# as all the rhs arguments. Only check the first argument of each
|
31
|
-
# side.
|
32
|
-
lhs_arg = @comparator.lhs_args[0]
|
33
|
-
rhs_arg = @comparator.rhs_args[0]
|
34
|
-
if !lhs_arg.datasets_equal?(rhs_arg)
|
35
|
-
@kind = :dual
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
@kind
|
40
|
-
end
|
41
|
-
|
42
|
-
def apply_to(dataset, side)
|
43
|
-
exprs =
|
44
|
-
case side
|
45
|
-
when :lhs
|
46
|
-
comparator.lhs_args.collect { |arg| arg.to_expr.as(arg.name) }
|
47
|
-
when :rhs
|
48
|
-
comparator.rhs_args.collect { |arg| arg.to_expr.as(arg.name) }
|
49
|
-
end
|
50
|
-
dataset.select_more(*exprs)
|
51
|
-
end
|
52
|
-
|
53
|
-
def satisfied?(score)
|
54
|
-
case mode
|
55
|
-
when :equal
|
56
|
-
score == threshold
|
57
|
-
when :min
|
58
|
-
score >= threshold
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
@@ -1,168 +0,0 @@
|
|
1
|
-
module Linkage
|
2
|
-
module Expectations
|
3
|
-
class Simple < Expectation
|
4
|
-
# The dataset this expectation applies to: `:lhs` or `:rhs`. This
|
5
|
-
# only applies to filter expectations.
|
6
|
-
# @return [Symbol]
|
7
|
-
attr_reader :side
|
8
|
-
|
9
|
-
attr_reader :meta_object_1, :meta_object_2, :operator
|
10
|
-
|
11
|
-
VALID_OPERATORS = [:==, :'!=', :>, :<, :>=, :<=]
|
12
|
-
|
13
|
-
# Automatically create an expectation type depending on the arguments.
|
14
|
-
#
|
15
|
-
# @param [Linkage::MetaObject] meta_object_1
|
16
|
-
# @param [Linkage::MetaObject] meta_object_2
|
17
|
-
# @param [Symbol] operator Valid operators: `:==`, `:'!='`, `:>`, `:<`, `:>=`, `:<=`
|
18
|
-
def self.create(meta_object_1, meta_object_2, operator)
|
19
|
-
klass =
|
20
|
-
if meta_object_1.static? && meta_object_2.static?
|
21
|
-
raise ArgumentError, "An expectation with two static objects is invalid"
|
22
|
-
elsif meta_object_1.static? || meta_object_2.static?
|
23
|
-
Filter
|
24
|
-
elsif meta_object_1.side == meta_object_2.side
|
25
|
-
if !meta_object_1.datasets_equal?(meta_object_2)
|
26
|
-
raise ArgumentError, "An expectation with two dynamic objects with the same side but different datasets is invalid"
|
27
|
-
end
|
28
|
-
Filter
|
29
|
-
elsif meta_object_1.objects_equal?(meta_object_2)
|
30
|
-
Self
|
31
|
-
elsif meta_object_1.datasets_equal?(meta_object_2)
|
32
|
-
Cross
|
33
|
-
else
|
34
|
-
Dual
|
35
|
-
end
|
36
|
-
|
37
|
-
klass.new(meta_object_1, meta_object_2, operator)
|
38
|
-
end
|
39
|
-
|
40
|
-
# Creates a new Simple.
|
41
|
-
#
|
42
|
-
# @param [Linkage::MetaObject] meta_object_1
|
43
|
-
# @param [Linkage::MetaObject] meta_object_2
|
44
|
-
# @param [Symbol] operator Valid operators: `:==`, `:'!='`, `:>`, `:<`, `:>=`, `:<=`
|
45
|
-
def initialize(meta_object_1, meta_object_2, operator)
|
46
|
-
@meta_object_1 = meta_object_1
|
47
|
-
@meta_object_2 = meta_object_2
|
48
|
-
@operator = operator
|
49
|
-
|
50
|
-
if !VALID_OPERATORS.include?(operator)
|
51
|
-
raise ArgumentError, "Invalid operator: #{operator.inspect}"
|
52
|
-
end
|
53
|
-
|
54
|
-
after_initialize
|
55
|
-
end
|
56
|
-
|
57
|
-
def same_except_side?(other)
|
58
|
-
other.is_a?(Simple) &&
|
59
|
-
operator == other.operator &&
|
60
|
-
meta_object_1.objects_equal?(other.meta_object_1) &&
|
61
|
-
meta_object_2.objects_equal?(other.meta_object_2)
|
62
|
-
end
|
63
|
-
|
64
|
-
def exactly!
|
65
|
-
function_1 = Function['binary'].new(@meta_object_1.object, :dataset => @meta_object_1.dataset)
|
66
|
-
function_2 = Function['binary'].new(@meta_object_2.object, :dataset => @meta_object_2.dataset)
|
67
|
-
@meta_object_1 = MetaObject.new(function_1, @meta_object_1.side)
|
68
|
-
@meta_object_2 = MetaObject.new(function_2, @meta_object_2.side)
|
69
|
-
end
|
70
|
-
|
71
|
-
# Display any warnings about this expectation.
|
72
|
-
def display_warnings
|
73
|
-
end
|
74
|
-
|
75
|
-
def decollation_needed?
|
76
|
-
merged_field.ruby_type[:type] == String && (
|
77
|
-
@meta_object_1.collation != @meta_object_2.collation ||
|
78
|
-
@meta_object_1.database_type != @meta_object_2.database_type
|
79
|
-
)
|
80
|
-
end
|
81
|
-
|
82
|
-
protected
|
83
|
-
|
84
|
-
def after_initialize
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
class Filter < Simple
|
89
|
-
def kind; :filter; end
|
90
|
-
|
91
|
-
def to_expr
|
92
|
-
case @operator
|
93
|
-
when :==, :'!='
|
94
|
-
expr = { @meta_object_1.to_expr => @meta_object_2.to_expr }
|
95
|
-
@operator == :== ? expr : ~expr
|
96
|
-
else
|
97
|
-
Sequel::SQL::BooleanExpression.new(@operator,
|
98
|
-
@meta_object_1.to_identifier, @meta_object_2.to_identifier)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def apply_to(dataset, side)
|
103
|
-
if side != @side
|
104
|
-
return dataset
|
105
|
-
end
|
106
|
-
|
107
|
-
dataset.filter(self.to_expr)
|
108
|
-
end
|
109
|
-
|
110
|
-
def decollation_needed?
|
111
|
-
false
|
112
|
-
end
|
113
|
-
|
114
|
-
private
|
115
|
-
|
116
|
-
def after_initialize
|
117
|
-
super
|
118
|
-
@side = @meta_object_1.static? ? @meta_object_2.side : @meta_object_1.side
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
class Match < Simple
|
123
|
-
def apply_to(dataset, side)
|
124
|
-
target =
|
125
|
-
if @meta_object_1.side == side
|
126
|
-
@meta_object_1
|
127
|
-
elsif @meta_object_2.side == side
|
128
|
-
@meta_object_2
|
129
|
-
else
|
130
|
-
raise ArgumentError, "Invalid `side` argument: #{side}"
|
131
|
-
end
|
132
|
-
|
133
|
-
dataset.group_match_more({
|
134
|
-
:meta_object => target,
|
135
|
-
:alias => merged_field.name
|
136
|
-
})
|
137
|
-
end
|
138
|
-
|
139
|
-
def merged_field
|
140
|
-
@merged_field ||= @meta_object_1.merge(@meta_object_2)
|
141
|
-
end
|
142
|
-
|
143
|
-
def display_warnings
|
144
|
-
object_1 = @meta_object_1.object
|
145
|
-
object_2 = @meta_object_2.object
|
146
|
-
if object_1.ruby_type[:type] == String && object_2.ruby_type[:type] == String
|
147
|
-
if @meta_object_1.dataset.database_type != @meta_object_2.dataset.database_type
|
148
|
-
warn "NOTE: You are comparing two string fields (#{object_1.name} and #{object_2.name}) from different databases. This may result in unexpected results, as different databases compare strings differently. Consider using the =binary= function."
|
149
|
-
elsif object_1.respond_to?(:collation) && object_1.respond_to?(:collation) && object_1.collation != object_2.collation
|
150
|
-
warn "NOTE: The two string fields you are comparing (#{object_1.name} and #{object_2.name}) have different collations (#{ldata.collation} vs. #{rdata.collation}). This may result in unexpected results, as the database may compare them differently. Consider using the =exactly= method."
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
class Self < Match
|
157
|
-
def kind; :self; end
|
158
|
-
end
|
159
|
-
|
160
|
-
class Cross < Match
|
161
|
-
def kind; :cross; end
|
162
|
-
end
|
163
|
-
|
164
|
-
class Dual < Match
|
165
|
-
def kind; :dual; end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
data/lib/linkage/function.rb
DELETED
@@ -1,148 +0,0 @@
|
|
1
|
-
module Linkage
|
2
|
-
# Abstract class to represent SQL functions. No attempts are made to
|
3
|
-
# ensure that the function actually exists in the database you're using.
|
4
|
-
#
|
5
|
-
# @abstract
|
6
|
-
class Function < Data
|
7
|
-
# Register a new function.
|
8
|
-
#
|
9
|
-
# @param [Class] klass Function class (probably a subclass of {Function})
|
10
|
-
def self.register(klass)
|
11
|
-
if klass.instance_methods(false).any? { |m| m.to_s == "ruby_type" }
|
12
|
-
@functions ||= {}
|
13
|
-
@functions[klass.function_name] = klass
|
14
|
-
else
|
15
|
-
raise ArgumentError, "ruby_type instance method must be defined"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.[](name)
|
20
|
-
@functions ? @functions[name] : nil
|
21
|
-
end
|
22
|
-
|
23
|
-
# Subclasses must define this.
|
24
|
-
def self.function_name
|
25
|
-
raise NotImplementedError
|
26
|
-
end
|
27
|
-
|
28
|
-
# Subclasses can define this to require a specific number of arguments
|
29
|
-
# of a certain class. To require two parameters of either String or
|
30
|
-
# Integer, do something like this:
|
31
|
-
#
|
32
|
-
# @@parameters = [[String, Integer], [String, Integer]]
|
33
|
-
# def self.parameters
|
34
|
-
# @@parameters
|
35
|
-
# end
|
36
|
-
#
|
37
|
-
def self.parameters
|
38
|
-
nil
|
39
|
-
end
|
40
|
-
|
41
|
-
attr_reader :args
|
42
|
-
|
43
|
-
# Creates a new Function object. If the arguments contain only
|
44
|
-
# static objects, you should specify the dataset that this function
|
45
|
-
# belongs to as the last argument like so:
|
46
|
-
#
|
47
|
-
# Function.new(foo, bar, :dataset => dataset)
|
48
|
-
#
|
49
|
-
# Optionally, you can use the `dataset=` setter to do it later. Many
|
50
|
-
# functions require a dataset to work properly. If you try to use
|
51
|
-
# such a function without setting a dataset, it will raise a RuntimeError.
|
52
|
-
#
|
53
|
-
# @param [Linkage::Data, Object] args Function arguments
|
54
|
-
def initialize(*args)
|
55
|
-
@names = [self.class.function_name]
|
56
|
-
@args = args
|
57
|
-
@options = args.last.is_a?(Hash) ? args.pop : {}
|
58
|
-
process_args
|
59
|
-
end
|
60
|
-
|
61
|
-
def name
|
62
|
-
@name ||= @names.join("_").to_sym
|
63
|
-
end
|
64
|
-
|
65
|
-
def dataset
|
66
|
-
if @dataset.nil?
|
67
|
-
raise RuntimeError, "You must specify a dataset for static functions"
|
68
|
-
end
|
69
|
-
@dataset
|
70
|
-
end
|
71
|
-
|
72
|
-
def dataset=(dataset)
|
73
|
-
@dataset = dataset
|
74
|
-
end
|
75
|
-
|
76
|
-
def static?
|
77
|
-
@static
|
78
|
-
end
|
79
|
-
|
80
|
-
def ==(other)
|
81
|
-
equal?(other) || (other.is_a?(Function) && name == other.name && args == other.args && dataset == other.dataset)
|
82
|
-
end
|
83
|
-
|
84
|
-
# Subclasses must define this. The return value should be a Hash with
|
85
|
-
# the following elements:
|
86
|
-
# :type - column type (Ruby class) of the result
|
87
|
-
# :opts - Optional hash with additional options (like :size)
|
88
|
-
def ruby_type
|
89
|
-
raise NotImplementedError
|
90
|
-
end
|
91
|
-
|
92
|
-
# Returns `nil` by default. Subclasses should redefine this if
|
93
|
-
# there is a collation.
|
94
|
-
def collation
|
95
|
-
nil
|
96
|
-
end
|
97
|
-
|
98
|
-
# @return [Sequel::SQL::Function]
|
99
|
-
def to_expr(options = {})
|
100
|
-
self.class.function_name.to_sym.sql_function(*@values)
|
101
|
-
end
|
102
|
-
|
103
|
-
private
|
104
|
-
|
105
|
-
def process_args
|
106
|
-
parameters = self.class.parameters
|
107
|
-
if parameters && parameters.length != @args.length
|
108
|
-
raise ArgumentError, "wrong number of arguments (#{@args.length} for #{parameters.length})"
|
109
|
-
end
|
110
|
-
|
111
|
-
@static = true
|
112
|
-
@values = []
|
113
|
-
@args.each_with_index do |arg, i|
|
114
|
-
if arg.kind_of?(Data)
|
115
|
-
@names << arg.name
|
116
|
-
@static &&= arg.static?
|
117
|
-
|
118
|
-
# possibly set dataset
|
119
|
-
if @dataset.nil?
|
120
|
-
@dataset = arg.dataset
|
121
|
-
elsif @dataset != arg.dataset
|
122
|
-
raise ArgumentError, "Using dynamic data sources with different datasets is not permitted"
|
123
|
-
end
|
124
|
-
|
125
|
-
type = arg.ruby_type[:type]
|
126
|
-
value = arg.to_expr
|
127
|
-
else
|
128
|
-
@names << arg.to_s.gsub(/\W/, "")
|
129
|
-
type = arg.class
|
130
|
-
value = arg
|
131
|
-
end
|
132
|
-
if parameters && parameters[i] != [:any] && !parameters[i].include?(type)
|
133
|
-
raise TypeError, "expected type #{parameters[i].join(" or ")}, got #{type}"
|
134
|
-
end
|
135
|
-
@values << value
|
136
|
-
end
|
137
|
-
|
138
|
-
if @dataset.nil? && @options[:dataset]
|
139
|
-
# Set dataset for static functions manually
|
140
|
-
@dataset = @options[:dataset]
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), "functions", "*.rb"))).each do |filename|
|
147
|
-
require filename
|
148
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
module Linkage
|
2
|
-
module Functions
|
3
|
-
class Binary < Function
|
4
|
-
def self.function_name
|
5
|
-
"binary"
|
6
|
-
end
|
7
|
-
|
8
|
-
def self.parameters
|
9
|
-
[[String]]
|
10
|
-
end
|
11
|
-
|
12
|
-
def ruby_type
|
13
|
-
{:type => File}
|
14
|
-
end
|
15
|
-
|
16
|
-
def to_expr(options = {})
|
17
|
-
expr =
|
18
|
-
case dataset.database_type
|
19
|
-
when :sqlite
|
20
|
-
@values[0].cast(:blob)
|
21
|
-
when :postgres
|
22
|
-
@values[0].cast(:bytea)
|
23
|
-
else
|
24
|
-
@values[0].cast(:binary)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
Function.register(Binary)
|
29
|
-
end
|
30
|
-
end
|
@@ -1,54 +0,0 @@
|
|
1
|
-
module Linkage
|
2
|
-
module Functions
|
3
|
-
class Cast < Function
|
4
|
-
def self.function_name
|
5
|
-
"cast"
|
6
|
-
end
|
7
|
-
|
8
|
-
def self.parameters
|
9
|
-
[[:any], [String]]
|
10
|
-
end
|
11
|
-
|
12
|
-
def ruby_type
|
13
|
-
type =
|
14
|
-
case @values[1]
|
15
|
-
when 'integer'
|
16
|
-
Fixnum
|
17
|
-
when 'binary'
|
18
|
-
File
|
19
|
-
else
|
20
|
-
raise "unknown type: #{@values[1]}"
|
21
|
-
end
|
22
|
-
|
23
|
-
{:type => type}
|
24
|
-
end
|
25
|
-
|
26
|
-
def to_expr(options = {})
|
27
|
-
cast =
|
28
|
-
case @values[1]
|
29
|
-
when 'integer'
|
30
|
-
case dataset.database_type
|
31
|
-
when :sqlite, :postgres, :h2
|
32
|
-
:integer
|
33
|
-
when :mysql
|
34
|
-
:signed
|
35
|
-
end
|
36
|
-
when 'binary'
|
37
|
-
case dataset.database_type
|
38
|
-
when :sqlite
|
39
|
-
:blob
|
40
|
-
when :postgres
|
41
|
-
:bytea
|
42
|
-
when :mysql, :h2
|
43
|
-
:binary
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
if cast
|
48
|
-
@values[0].cast(cast)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
Function.register(Cast)
|
53
|
-
end
|
54
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module Linkage
|
2
|
-
module Functions
|
3
|
-
# Returns the number of characters in a string.
|
4
|
-
class Length < Function
|
5
|
-
def self.function_name
|
6
|
-
"length"
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.parameters
|
10
|
-
[[String]]
|
11
|
-
end
|
12
|
-
|
13
|
-
def ruby_type
|
14
|
-
{:type => Fixnum}
|
15
|
-
end
|
16
|
-
|
17
|
-
def to_expr(options = {})
|
18
|
-
expr =
|
19
|
-
case dataset.database_type
|
20
|
-
when :mysql, :postgres
|
21
|
-
:char_length.sql_function(@values[0])
|
22
|
-
else
|
23
|
-
:length.sql_function(@values[0])
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
Function.register(Length)
|
28
|
-
end
|
29
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
module Linkage
|
2
|
-
module Functions
|
3
|
-
class Strftime < Function
|
4
|
-
def self.function_name
|
5
|
-
"strftime"
|
6
|
-
end
|
7
|
-
|
8
|
-
def self.parameters
|
9
|
-
[[Date, Time, DateTime], [String]]
|
10
|
-
end
|
11
|
-
|
12
|
-
def ruby_type
|
13
|
-
# TODO: string length needed
|
14
|
-
{:type => String}
|
15
|
-
end
|
16
|
-
|
17
|
-
def to_expr(options = {})
|
18
|
-
expr =
|
19
|
-
case dataset.database_type
|
20
|
-
when :mysql
|
21
|
-
:date_format.sql_function(*@values)
|
22
|
-
when :sqlite
|
23
|
-
:strftime.sql_function(@values[1], @values[0])
|
24
|
-
when :postgres
|
25
|
-
:to_char.sql_function(*@values)
|
26
|
-
else
|
27
|
-
:strftime.sql_function(@values[0], @values[1])
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
Function.register(Strftime)
|
32
|
-
end
|
33
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
module Linkage
|
2
|
-
module Functions
|
3
|
-
class Trim < Function
|
4
|
-
def self.function_name
|
5
|
-
"trim"
|
6
|
-
end
|
7
|
-
|
8
|
-
def self.parameters
|
9
|
-
[[String]]
|
10
|
-
end
|
11
|
-
|
12
|
-
def ruby_type
|
13
|
-
if @args[0].kind_of?(Data)
|
14
|
-
@args[0].ruby_type
|
15
|
-
else
|
16
|
-
{:type => String}
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def collation
|
21
|
-
if @args[0].kind_of?(Data)
|
22
|
-
@args[0].collation
|
23
|
-
else
|
24
|
-
super
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
Function.register(Trim)
|
29
|
-
end
|
30
|
-
end
|