fluent-query 0.9.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.
- data/.document +5 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +22 -0
- data/LICENSE.txt +20 -0
- data/README.md +230 -0
- data/Rakefile +29 -0
- data/VERSION +1 -0
- data/fluent-query.gemspec +75 -0
- data/lib/fluent-query.rb +2 -0
- data/lib/fluent-query/compiler.rb +182 -0
- data/lib/fluent-query/compilers/result.rb +33 -0
- data/lib/fluent-query/connection.rb +316 -0
- data/lib/fluent-query/data.rb +17 -0
- data/lib/fluent-query/driver.rb +279 -0
- data/lib/fluent-query/drivers/exception.rb +13 -0
- data/lib/fluent-query/drivers/result.rb +98 -0
- data/lib/fluent-query/exception.rb +12 -0
- data/lib/fluent-query/queries/abstract.rb +169 -0
- data/lib/fluent-query/queries/compiled.rb +51 -0
- data/lib/fluent-query/queries/prepared.rb +50 -0
- data/lib/fluent-query/queries/processor.rb +392 -0
- data/lib/fluent-query/query.rb +118 -0
- data/lib/fluent-query/result.rb +153 -0
- data/lib/fluent-query/token.rb +73 -0
- data/lib/fluent-query/tokens/raw.rb +26 -0
- metadata +136 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "abstract"
|
3
|
+
|
4
|
+
module FluentQuery
|
5
|
+
module Drivers
|
6
|
+
|
7
|
+
##
|
8
|
+
# Represents general driver result accessor.
|
9
|
+
#
|
10
|
+
|
11
|
+
class Result
|
12
|
+
|
13
|
+
##
|
14
|
+
# Initializes result.
|
15
|
+
#
|
16
|
+
|
17
|
+
public
|
18
|
+
def initialize
|
19
|
+
if self.instance_of? FluentQuery::Drivers::Result
|
20
|
+
not_implemented
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Returns all selected rows.
|
26
|
+
#
|
27
|
+
|
28
|
+
public
|
29
|
+
def all
|
30
|
+
not_implemented
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Returns one row.
|
35
|
+
#
|
36
|
+
|
37
|
+
public
|
38
|
+
def one
|
39
|
+
not_implemented
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Returns first value of first row.
|
44
|
+
#
|
45
|
+
|
46
|
+
public
|
47
|
+
def single
|
48
|
+
not_implemented
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Returns row as hash.
|
53
|
+
#
|
54
|
+
|
55
|
+
public
|
56
|
+
def hash
|
57
|
+
not_implemented
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Handles iterating.
|
62
|
+
#
|
63
|
+
|
64
|
+
public
|
65
|
+
def each
|
66
|
+
not_implemented
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
##
|
71
|
+
# Repeats the query leaded to the result.
|
72
|
+
#
|
73
|
+
|
74
|
+
public
|
75
|
+
def repeat!
|
76
|
+
not_implemented
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Returns rows count.
|
81
|
+
#
|
82
|
+
|
83
|
+
public
|
84
|
+
def count
|
85
|
+
not_implemented
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Frees result resources.
|
90
|
+
#
|
91
|
+
|
92
|
+
public
|
93
|
+
def free!
|
94
|
+
not_implemented
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "fluent-query/queries/processor"
|
3
|
+
require "abstract"
|
4
|
+
|
5
|
+
module FluentQuery
|
6
|
+
module Queries
|
7
|
+
|
8
|
+
##
|
9
|
+
# Represents and abstract query.
|
10
|
+
#
|
11
|
+
|
12
|
+
class Abstract
|
13
|
+
|
14
|
+
##
|
15
|
+
# Holds connection object.
|
16
|
+
#
|
17
|
+
|
18
|
+
@connection
|
19
|
+
attr_reader :connection
|
20
|
+
|
21
|
+
##
|
22
|
+
# Holds appropriate query processor.
|
23
|
+
#
|
24
|
+
|
25
|
+
@processor
|
26
|
+
|
27
|
+
##
|
28
|
+
# Constructor.
|
29
|
+
#
|
30
|
+
|
31
|
+
public
|
32
|
+
def initialize(connection)
|
33
|
+
@connection = connection
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Returns processor.
|
38
|
+
#
|
39
|
+
|
40
|
+
public
|
41
|
+
def processor
|
42
|
+
if not @processor
|
43
|
+
driver = @connection.driver
|
44
|
+
@processor = FluentQuery::Queries::Processor::new(driver)
|
45
|
+
end
|
46
|
+
|
47
|
+
return @processor
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Builds prepared query string to final form.
|
52
|
+
#
|
53
|
+
|
54
|
+
public
|
55
|
+
def build(*args)
|
56
|
+
not_implemented
|
57
|
+
end
|
58
|
+
|
59
|
+
alias :"build!" :build
|
60
|
+
|
61
|
+
##
|
62
|
+
# Executes query and returns result object.
|
63
|
+
#
|
64
|
+
|
65
|
+
public
|
66
|
+
def execute(*args)
|
67
|
+
@connection.execute(self.build(*args))
|
68
|
+
end
|
69
|
+
|
70
|
+
alias :"execute!" :execute
|
71
|
+
|
72
|
+
##
|
73
|
+
# Executes query and returns count of changed/inserted rows.
|
74
|
+
#
|
75
|
+
|
76
|
+
public
|
77
|
+
def do(*args)
|
78
|
+
@connection.do(self.build(*args))
|
79
|
+
end
|
80
|
+
|
81
|
+
alias :"do!" :do
|
82
|
+
|
83
|
+
|
84
|
+
##
|
85
|
+
# Returns all selected rows.
|
86
|
+
#
|
87
|
+
|
88
|
+
public
|
89
|
+
def all(*args)
|
90
|
+
self.execute(*args).all
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
##
|
95
|
+
# Returns one row.
|
96
|
+
#
|
97
|
+
|
98
|
+
public
|
99
|
+
def one(*args)
|
100
|
+
self.execute(*args).one
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Returns all selected rows ordered according to datafield from it.
|
105
|
+
#
|
106
|
+
|
107
|
+
public
|
108
|
+
def assoc(*specification, &block)
|
109
|
+
self.execute.assoc(*specification, &block)
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Returns first value of first row.
|
114
|
+
#
|
115
|
+
|
116
|
+
public
|
117
|
+
def single(*args)
|
118
|
+
self.execute(*args).single
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Handles iterating.
|
123
|
+
#
|
124
|
+
|
125
|
+
public
|
126
|
+
def each(*args)
|
127
|
+
self.execute(*args).each do |item|
|
128
|
+
yield item
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
##
|
133
|
+
# Maps callback to array.
|
134
|
+
#
|
135
|
+
|
136
|
+
public
|
137
|
+
def map(*args, &block)
|
138
|
+
result = [ ]
|
139
|
+
|
140
|
+
self.each(*args) do |item|
|
141
|
+
result << block.call(item)
|
142
|
+
end
|
143
|
+
|
144
|
+
return result
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
# Returns only rows for which block is true.
|
149
|
+
#
|
150
|
+
|
151
|
+
public
|
152
|
+
def find_all(*args, &block)
|
153
|
+
result = [ ]
|
154
|
+
|
155
|
+
self.each(*args) do |item|
|
156
|
+
if block.call(item) === true
|
157
|
+
result << block.call(item)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
return result
|
162
|
+
end
|
163
|
+
|
164
|
+
alias :filter :find_all
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "fluent-query/queries/abstract"
|
3
|
+
|
4
|
+
module FluentQuery
|
5
|
+
module Queries
|
6
|
+
|
7
|
+
##
|
8
|
+
# Compiled query.
|
9
|
+
#
|
10
|
+
|
11
|
+
class Compiled < FluentQuery::Queries::Abstract
|
12
|
+
|
13
|
+
##
|
14
|
+
# Holds query in compiled form.
|
15
|
+
#
|
16
|
+
|
17
|
+
@raw
|
18
|
+
attr_accessor :raw
|
19
|
+
|
20
|
+
##
|
21
|
+
# Constructor.
|
22
|
+
#
|
23
|
+
|
24
|
+
public
|
25
|
+
def initialize(connection, query)
|
26
|
+
super(connection)
|
27
|
+
@raw = query.processor.compile(@connection.driver.build_query(query, :compile))
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Builds prepared query string to final form.
|
32
|
+
#
|
33
|
+
|
34
|
+
public
|
35
|
+
def build(*args)
|
36
|
+
@raw.complete(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Returns all selected rows ordered according to datafield from it.
|
41
|
+
#
|
42
|
+
|
43
|
+
public
|
44
|
+
def assoc(specification, *args)
|
45
|
+
self.execute(*args).assoc(specification)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "fluent-query/queries/abstract"
|
3
|
+
|
4
|
+
module FluentQuery
|
5
|
+
module Queries
|
6
|
+
|
7
|
+
##
|
8
|
+
# Compiled query.
|
9
|
+
#
|
10
|
+
|
11
|
+
class Prepared < FluentQuery::Queries::Abstract
|
12
|
+
|
13
|
+
##
|
14
|
+
# Holds query in prepared form.
|
15
|
+
#
|
16
|
+
|
17
|
+
@query
|
18
|
+
|
19
|
+
##
|
20
|
+
# Constructor.
|
21
|
+
#
|
22
|
+
|
23
|
+
public
|
24
|
+
def initialize(connection, query)
|
25
|
+
super(connection)
|
26
|
+
@query = @connection.driver.prepare(query)
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Builds prepared query string to final form.
|
31
|
+
#
|
32
|
+
|
33
|
+
public
|
34
|
+
def build(*args)
|
35
|
+
[@query, args]
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Returns all selected rows ordered according to datafield from it.
|
40
|
+
#
|
41
|
+
|
42
|
+
public
|
43
|
+
def assoc(specification, *args)
|
44
|
+
self.execute(*args).assoc(specification)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,392 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "date"
|
3
|
+
require "hash-utils/string"
|
4
|
+
|
5
|
+
require "fluent-query/query"
|
6
|
+
require "fluent-query/compiler"
|
7
|
+
|
8
|
+
module FluentQuery
|
9
|
+
module Queries
|
10
|
+
|
11
|
+
##
|
12
|
+
# Query processor.
|
13
|
+
#
|
14
|
+
# Its primary aim is to dive processing methods from query object as is.
|
15
|
+
# In fact, defines something as processing session.
|
16
|
+
#
|
17
|
+
|
18
|
+
class Processor
|
19
|
+
|
20
|
+
##
|
21
|
+
# Indicates column replacement to perform.
|
22
|
+
# (Flag.)
|
23
|
+
#
|
24
|
+
|
25
|
+
COLUMN_REPLACEMENT = 2
|
26
|
+
|
27
|
+
##
|
28
|
+
# Indicates string replacement to perform.
|
29
|
+
# (Flag.)
|
30
|
+
#
|
31
|
+
|
32
|
+
STRING_REPLACEMENT = 1
|
33
|
+
|
34
|
+
##
|
35
|
+
# Indicates formatting directive replacements to perform.
|
36
|
+
# (Flag.)
|
37
|
+
#
|
38
|
+
|
39
|
+
FORMATTING_REPLACEMENT = 4
|
40
|
+
|
41
|
+
##
|
42
|
+
# Describes formatting directive.
|
43
|
+
# @var Regexp
|
44
|
+
#
|
45
|
+
|
46
|
+
FORMATTING_DIRECTIVE = /%%(?:[islbfdt]|sql|and|or)(?:[^\w]|$)/
|
47
|
+
|
48
|
+
##
|
49
|
+
# Describes column directive.
|
50
|
+
# @var Regexp
|
51
|
+
#
|
52
|
+
|
53
|
+
COLUMN_DIRECTIVE = /(?:^|\.|[^\'"\w\\])?\[(?:[\w\.]*[^\\\W])\](?:[^\'"\w]|\.|$)/ # problem is, it interpretes regex
|
54
|
+
# from left to right, so if second
|
55
|
+
# occurence is immedieately
|
56
|
+
# it will not be matched -- that
|
57
|
+
# is reason for ? in the begin section
|
58
|
+
|
59
|
+
##
|
60
|
+
# Describes string directive.
|
61
|
+
# @var Regexp
|
62
|
+
#
|
63
|
+
|
64
|
+
STRING_DIRECTIVE = /(?:^|[^\\])"/
|
65
|
+
|
66
|
+
##
|
67
|
+
# Holds simple column definition.
|
68
|
+
# @var Regexp
|
69
|
+
#
|
70
|
+
|
71
|
+
FORMATTING_DIRECTIVE_SIMPLE = /%%\w+/
|
72
|
+
|
73
|
+
##
|
74
|
+
# Holds simple column definition.
|
75
|
+
#
|
76
|
+
|
77
|
+
COLUMN_DIRECTIVE_SIMPLE = /\[.+\]/
|
78
|
+
|
79
|
+
##
|
80
|
+
# Final regexp for incremental fluent expanding.
|
81
|
+
# (internal cache)
|
82
|
+
|
83
|
+
@__fluent_expander
|
84
|
+
|
85
|
+
##
|
86
|
+
# Final regexp for incremental fluent replacing.
|
87
|
+
# (internal cache)
|
88
|
+
|
89
|
+
@__fluent_replacer
|
90
|
+
|
91
|
+
##
|
92
|
+
# Driver upon which processor should work upon.
|
93
|
+
#
|
94
|
+
|
95
|
+
@driver
|
96
|
+
attr_reader :driver
|
97
|
+
|
98
|
+
##
|
99
|
+
# Compiler associated to this processor.
|
100
|
+
#
|
101
|
+
|
102
|
+
@_compiler
|
103
|
+
|
104
|
+
##
|
105
|
+
# Constructs processor.
|
106
|
+
# @param MP_Fluent_Driver driver Driver upon which processor should work upon.
|
107
|
+
#
|
108
|
+
|
109
|
+
public
|
110
|
+
def initialize(driver)
|
111
|
+
@driver = driver
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Process array to some reasonable string form.
|
116
|
+
#
|
117
|
+
# Joins its elements and separates them by commas. Quotes strings
|
118
|
+
# by by-driver-defined string quoting and integers by integer quoting.
|
119
|
+
#
|
120
|
+
|
121
|
+
public
|
122
|
+
def process_array(array, glue = ",")
|
123
|
+
result = [ ]
|
124
|
+
|
125
|
+
array.each do |item|
|
126
|
+
result << self.quote_value(item)
|
127
|
+
end
|
128
|
+
|
129
|
+
return result.join(glue + " ")
|
130
|
+
end
|
131
|
+
|
132
|
+
##
|
133
|
+
# Process hash to some reasonable string form.
|
134
|
+
#
|
135
|
+
# Joins its elements to name = value form and separates them
|
136
|
+
# by commas. Quotes string value by by-driver-defined string quoting
|
137
|
+
# and integers by integer quoting. Quotes name by identifiers quoting.
|
138
|
+
#
|
139
|
+
# Handles two modes:
|
140
|
+
# * "assigning" which classicaly keeps for example the '=' operator,
|
141
|
+
# * "comparing" which sets for example 'IS' operator for booleans.
|
142
|
+
#
|
143
|
+
|
144
|
+
public
|
145
|
+
def process_hash(hash, glue = ",", equality = :comparing)
|
146
|
+
result = [ ]
|
147
|
+
mode = equality
|
148
|
+
|
149
|
+
hash.each_pair do |key, value|
|
150
|
+
operator = @driver.quote_equality(value, mode)
|
151
|
+
key = self.quote_identifier(key)
|
152
|
+
value = self.quote_value(value)
|
153
|
+
|
154
|
+
result << (key + " " + operator + " " + value)
|
155
|
+
end
|
156
|
+
|
157
|
+
return result.join(" " << glue << " ")
|
158
|
+
end
|
159
|
+
|
160
|
+
##
|
161
|
+
# Quotes string by driver string quoting and integers by integer
|
162
|
+
# quoting. Other values are converted to string too and
|
163
|
+
# leaved unquoted.
|
164
|
+
#
|
165
|
+
|
166
|
+
public
|
167
|
+
def quote_value(value)
|
168
|
+
|
169
|
+
if (value.string?) or (value.symbol?)
|
170
|
+
result = @driver.quote_string(value.to_s)
|
171
|
+
elsif value.kind_of? Integer
|
172
|
+
result = @driver.quote_integer(value)
|
173
|
+
elsif value.array?
|
174
|
+
result = "(" << self.process_array(value) << ")" # TODO: question is, if we should do it here, if it's enough general just for processor
|
175
|
+
elsif value.kind_of? Float
|
176
|
+
result = @driver.quote_float(value)
|
177
|
+
elsif (value.kind_of? TrueClass) or (value.kind_of? FalseClass)
|
178
|
+
result = @driver.quote_boolean(value)
|
179
|
+
elsif (value.kind_of? Date) or (value.kind_of? DateTime)
|
180
|
+
result = @driver.quote_date_time(value)
|
181
|
+
elsif value.nil?
|
182
|
+
result = @driver.null
|
183
|
+
else
|
184
|
+
result = value.to_s
|
185
|
+
end
|
186
|
+
|
187
|
+
return result
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
##
|
192
|
+
# Quotes subquery by driver subquery quoting.
|
193
|
+
#
|
194
|
+
|
195
|
+
public
|
196
|
+
def quote_subquery(subquery)
|
197
|
+
if subquery.kind_of? FluentQuery::Query
|
198
|
+
subquery = subquery.build!
|
199
|
+
end
|
200
|
+
|
201
|
+
return @driver.quote_subquery(subquery)
|
202
|
+
end
|
203
|
+
|
204
|
+
##
|
205
|
+
# Quotes identifiers by identifier quoting.
|
206
|
+
#
|
207
|
+
|
208
|
+
public
|
209
|
+
def quote_identifier(identifier)
|
210
|
+
@driver.quote_identifier(identifier.to_s)
|
211
|
+
end
|
212
|
+
|
213
|
+
##
|
214
|
+
# Quotes identifiers list.
|
215
|
+
#
|
216
|
+
|
217
|
+
public
|
218
|
+
def quote_identifiers(identifiers)
|
219
|
+
identifiers.map { |i| self.quote_identifier(i) }
|
220
|
+
end
|
221
|
+
|
222
|
+
##
|
223
|
+
# Processes identifier list to the string representation.
|
224
|
+
# Quotes them and joins them separated by commas.
|
225
|
+
#
|
226
|
+
|
227
|
+
public
|
228
|
+
def process_identifiers(identifiers)
|
229
|
+
self.quote_identifiers(identifiers).join(", ")
|
230
|
+
end
|
231
|
+
|
232
|
+
##
|
233
|
+
# Processed strings with format definitions and data specifications.
|
234
|
+
#
|
235
|
+
# Mode can be :compile, :build or :finish. Compiling means building the
|
236
|
+
# query without expanding the formatting directives. Finishing means
|
237
|
+
# building the prepared query.
|
238
|
+
#
|
239
|
+
|
240
|
+
public
|
241
|
+
def process_formatted(sequence, mode = :build)
|
242
|
+
count = sequence.length
|
243
|
+
i = 0
|
244
|
+
output = ""
|
245
|
+
|
246
|
+
replacer_settings = self.class::COLUMN_REPLACEMENT | self.class::STRING_REPLACEMENT
|
247
|
+
expander_settings = self.class::FORMATTING_REPLACEMENT
|
248
|
+
|
249
|
+
while i < count
|
250
|
+
item = sequence[i]
|
251
|
+
|
252
|
+
##
|
253
|
+
|
254
|
+
if item.kind_of? String
|
255
|
+
item = item.dup
|
256
|
+
|
257
|
+
# Calls to each occurence of directive matching to directive
|
258
|
+
# finding expression directive processing. In each call increases
|
259
|
+
# sequence position counter so moves forward.
|
260
|
+
|
261
|
+
if mode != :finish
|
262
|
+
item.gsub!(self.replacer) do |directive|
|
263
|
+
directive.strip!
|
264
|
+
self.process_directive(directive, nil, replacer_settings)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
if mode != :compile
|
269
|
+
item.gsub!(self.expander) do |directive|
|
270
|
+
self.process_directive(directive, sequence[i += 1], expander_settings)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
output << item
|
275
|
+
|
276
|
+
elsif item.kind_of? Symbol
|
277
|
+
output << self.quote_identifier(item)
|
278
|
+
|
279
|
+
else
|
280
|
+
output << item.to_s
|
281
|
+
|
282
|
+
end
|
283
|
+
|
284
|
+
output << " "
|
285
|
+
i += 1
|
286
|
+
end
|
287
|
+
|
288
|
+
return output
|
289
|
+
|
290
|
+
end
|
291
|
+
|
292
|
+
##
|
293
|
+
# Processes fluent directive.
|
294
|
+
# Replacements is three bytes flag array.
|
295
|
+
#
|
296
|
+
|
297
|
+
public
|
298
|
+
def process_directive(directive, argument = nil, replacements = 7)
|
299
|
+
if (replacements & self.class::COLUMN_REPLACEMENT > 0) and (directive[0].ord == 91) # "[", Column directive
|
300
|
+
result = directive.gsub(self.class::COLUMN_DIRECTIVE_SIMPLE) { |value| self.quote_identifier(value[1..-2]) }
|
301
|
+
elsif (replacements & self.class::STRING_REPLACEMENT > 0) and (directive[-1].ord == 34) # "\"", String directive
|
302
|
+
result = directive.gsub('"', @driver.quote_string("").last)
|
303
|
+
elsif (replacements & self.class::FORMATTING_REPLACEMENT > 0) and (directive[0..1].to_sym == :"%%") # Formatting directive
|
304
|
+
result = directive.gsub(self.class::FORMATTING_DIRECTIVE_SIMPLE) { |value| self.process_formatting(value[2..-1], argument) }
|
305
|
+
else
|
306
|
+
result = directive
|
307
|
+
end
|
308
|
+
|
309
|
+
return result
|
310
|
+
end
|
311
|
+
|
312
|
+
##
|
313
|
+
# Processes formatting directive.
|
314
|
+
#
|
315
|
+
|
316
|
+
public
|
317
|
+
def process_formatting(directive, argument)
|
318
|
+
proc = self.compiler.compile_formatting(directive)
|
319
|
+
|
320
|
+
if proc
|
321
|
+
output = proc.call(argument)
|
322
|
+
else
|
323
|
+
output = ""
|
324
|
+
end
|
325
|
+
|
326
|
+
return output
|
327
|
+
end
|
328
|
+
|
329
|
+
##
|
330
|
+
# Returns NULL representation.
|
331
|
+
#
|
332
|
+
|
333
|
+
public
|
334
|
+
def null
|
335
|
+
@driver.null
|
336
|
+
end
|
337
|
+
|
338
|
+
##
|
339
|
+
# Returns final fluent expander query.
|
340
|
+
#
|
341
|
+
|
342
|
+
public
|
343
|
+
def expander
|
344
|
+
if @__fluent_expander.nil?
|
345
|
+
@__fluent_expander = self.class::FORMATTING_DIRECTIVE
|
346
|
+
end
|
347
|
+
|
348
|
+
@__fluent_expander
|
349
|
+
end
|
350
|
+
|
351
|
+
##
|
352
|
+
# Returns final fluent formatter query.
|
353
|
+
#
|
354
|
+
|
355
|
+
public
|
356
|
+
def replacer
|
357
|
+
if @__fluent_replacer.nil?
|
358
|
+
parts = Array[self.class::COLUMN_DIRECTIVE, self.class::STRING_DIRECTIVE]
|
359
|
+
|
360
|
+
result = ""
|
361
|
+
result << "(?:(?:" << parts.join(")|(?:") << "))"
|
362
|
+
|
363
|
+
@__fluent_replacer = Regexp::new(result)
|
364
|
+
end
|
365
|
+
|
366
|
+
@__fluent_replacer
|
367
|
+
end
|
368
|
+
|
369
|
+
##
|
370
|
+
# Returns compiler.
|
371
|
+
#
|
372
|
+
|
373
|
+
def compiler
|
374
|
+
if @_compiler.nil?
|
375
|
+
@_compiler = FluentQuery::Compiler::new(self)
|
376
|
+
end
|
377
|
+
|
378
|
+
@_compiler
|
379
|
+
end
|
380
|
+
|
381
|
+
##
|
382
|
+
# Compiles the string.
|
383
|
+
#
|
384
|
+
|
385
|
+
def compile(string)
|
386
|
+
self.compiler.compile(string)
|
387
|
+
end
|
388
|
+
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|