linkage 0.0.8 → 0.1.0.pre
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 +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
|