sequel-hexspace 1.0.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/LICENSE +18 -0
- data/README +3 -0
- data/lib/sequel/adapters/hexspace.rb +92 -0
- data/lib/sequel/adapters/shared/spark.rb +467 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b2949511f39f19c48b5120965d8b236fe93215c07ee9cf987d7b80f81d649b81
|
4
|
+
data.tar.gz: 68b65e86ccc1ad3cc1fef989eb1d79b7927f96c4f4b5a60abb9699c7b425f4a4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 021db16089f80aee9a8398b54747265e6eedb59713b7fac36316d4623a324ce96036e532df0ac75171ec47a04ade882ee168c5c773e5487a7591567bfaab707c
|
7
|
+
data.tar.gz: 2767b9661167bc70fb775513f4abcb0465a77cc19c16f9bdb94972acd30896a310c3daeb6d8a34cfdd089ccf6c8bd6558bc459a9ae2eb3415c7cfd35b5d7273a
|
data/LICENSE
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2023 Jeremy Evans
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'hexspace'
|
2
|
+
require_relative 'shared/spark'
|
3
|
+
|
4
|
+
module Sequel
|
5
|
+
module Hexspace
|
6
|
+
class Database < Sequel::Database
|
7
|
+
include Spark::DatabaseMethods
|
8
|
+
|
9
|
+
set_adapter_scheme :hexspace
|
10
|
+
|
11
|
+
ALLOWED_CLIENT_KEYWORDS = ::Hexspace::Client.instance_method(:initialize).parameters.map(&:last).freeze
|
12
|
+
|
13
|
+
def connect(server)
|
14
|
+
opts = server_opts(server)
|
15
|
+
opts[:username] = opts[:user]
|
16
|
+
opts.select!{|k,v| v.to_s != '' && ALLOWED_CLIENT_KEYWORDS.include?(k)}
|
17
|
+
::Hexspace::Client.new(**opts)
|
18
|
+
end
|
19
|
+
|
20
|
+
def dataset_class_default
|
21
|
+
Dataset
|
22
|
+
end
|
23
|
+
|
24
|
+
def disconnect_connection(conn)
|
25
|
+
# Hexspace does not appear to support a disconnection method
|
26
|
+
# To keep tests happy, mark the connection as invalid
|
27
|
+
conn.instance_variable_set(:@sequel_invalid, true)
|
28
|
+
end
|
29
|
+
|
30
|
+
def execute(sql, opts=OPTS)
|
31
|
+
synchronize(opts[:server]) do |conn|
|
32
|
+
res = log_connection_yield(sql, conn){conn.execute(sql, result_object: true)}
|
33
|
+
rescue => e
|
34
|
+
raise_error(e)
|
35
|
+
else
|
36
|
+
yield res if defined?(yield)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def execute_insert(sql, opts=OPTS)
|
41
|
+
execute(sql, opts)
|
42
|
+
|
43
|
+
# Return nil instead of empty array.
|
44
|
+
# Spark does not support primary keys nor autoincrementing values
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def valid_connection?(conn)
|
49
|
+
!conn.instance_variable_get(:@sequel_invalid)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Dataset < Sequel::Dataset
|
54
|
+
include Spark::DatasetMethods
|
55
|
+
|
56
|
+
def fetch_rows(sql)
|
57
|
+
execute(sql) do |result|
|
58
|
+
columns = result.columns.map(&:to_sym)
|
59
|
+
self.columns = columns
|
60
|
+
next if result.rows.empty?
|
61
|
+
|
62
|
+
types = result.column_types
|
63
|
+
column_info = columns.map.with_index do |name, i|
|
64
|
+
conversion_proc = case types[i]
|
65
|
+
when 'binary'
|
66
|
+
Sequel.method(:blob)
|
67
|
+
when 'timestamp'
|
68
|
+
db.method(:to_application_timestamp)
|
69
|
+
end
|
70
|
+
|
71
|
+
[i, name, conversion_proc]
|
72
|
+
end
|
73
|
+
|
74
|
+
result.rows.each do |row|
|
75
|
+
h = {}
|
76
|
+
column_info.each do |i, name, conversion_proc|
|
77
|
+
value = row[i]
|
78
|
+
h[name] = if value.nil?
|
79
|
+
nil
|
80
|
+
elsif conversion_proc
|
81
|
+
conversion_proc.call(value)
|
82
|
+
else
|
83
|
+
value
|
84
|
+
end
|
85
|
+
end
|
86
|
+
yield h
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,467 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'sequel/adapters/utils/unmodified_identifiers'
|
4
|
+
|
5
|
+
module Sequel
|
6
|
+
module Spark
|
7
|
+
Sequel::Database.set_shared_adapter_scheme(:spark, self)
|
8
|
+
|
9
|
+
module DatabaseMethods
|
10
|
+
include UnmodifiedIdentifiers::DatabaseMethods
|
11
|
+
|
12
|
+
def create_schema(schema_name, opts=OPTS)
|
13
|
+
run(create_schema_sql(schema_name, opts))
|
14
|
+
end
|
15
|
+
|
16
|
+
def database_type
|
17
|
+
:spark
|
18
|
+
end
|
19
|
+
|
20
|
+
def drop_schema(schema_name, opts=OPTS)
|
21
|
+
run(drop_schema_sql(schema_name, opts))
|
22
|
+
end
|
23
|
+
|
24
|
+
# Spark does not support primary keys, so do not
|
25
|
+
# add any options
|
26
|
+
def serial_primary_key_options
|
27
|
+
# We could raise an exception here instead of just
|
28
|
+
# ignoring the primary key setting.
|
29
|
+
{:type=>Integer}
|
30
|
+
end
|
31
|
+
|
32
|
+
def supports_create_table_if_not_exists?
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def tables(opts=OPTS)
|
37
|
+
_mangle_tables(_tables("TABLES", :tableName, opts) - _views(opts), opts)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Spark does not support transactions.
|
41
|
+
def transaction(opts=nil)
|
42
|
+
yield
|
43
|
+
end
|
44
|
+
|
45
|
+
# Use an inline VALUES table.
|
46
|
+
def values(v)
|
47
|
+
@default_dataset.clone(:values=>v)
|
48
|
+
end
|
49
|
+
|
50
|
+
def views(opts=OPTS)
|
51
|
+
_mangle_tables(_views(opts), opts)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def _tables(type, column, opts)
|
57
|
+
sql = String.new
|
58
|
+
sql << "SHOW " << type
|
59
|
+
if schema = opts[:schema]
|
60
|
+
sql << " IN " << literal(schema)
|
61
|
+
end
|
62
|
+
if like = opts[:like]
|
63
|
+
sql << " LIKE " << literal(like)
|
64
|
+
end
|
65
|
+
|
66
|
+
ds = dataset.with_sql(sql)
|
67
|
+
|
68
|
+
# Always internally qualify, so that if a table name in a schema
|
69
|
+
# has the same name as a temporary view, it will not exclude
|
70
|
+
# the table name.
|
71
|
+
ds.map([:namespace, column]).map do |ns, name|
|
72
|
+
if ns && !ns.empty?
|
73
|
+
Sequel::SQL::QualifiedIdentifier.new(ns, name)
|
74
|
+
else
|
75
|
+
name.to_sym
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def _views(opts)
|
81
|
+
_tables("VIEWS", :viewName, opts)
|
82
|
+
end
|
83
|
+
|
84
|
+
def _mangle_tables(tables, opts)
|
85
|
+
if opts[:qualify]
|
86
|
+
tables
|
87
|
+
else
|
88
|
+
tables.map{|t| t.is_a?(Sequel::SQL::QualifiedIdentifier) ? t.column.to_sym : t}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def create_schema_sql(schema_name, opts)
|
93
|
+
sql = String.new
|
94
|
+
sql << 'CREATE SCHEMA '
|
95
|
+
sql << 'IF NOT EXISTS ' if opts[:if_not_exists]
|
96
|
+
sql << literal(schema_name)
|
97
|
+
|
98
|
+
if comment = opts[:comment]
|
99
|
+
sql << ' COMMENT '
|
100
|
+
sql << literal(comment)
|
101
|
+
end
|
102
|
+
|
103
|
+
if location = opts[:location]
|
104
|
+
sql << ' LOCATION '
|
105
|
+
sql << literal(location)
|
106
|
+
end
|
107
|
+
|
108
|
+
if properties = opts[:properties]
|
109
|
+
sql << ' WITH DBPROPERTIES ('
|
110
|
+
properties.each do |k, v|
|
111
|
+
sql << literal(k.to_s) << "=" << literal(v.to_s)
|
112
|
+
end
|
113
|
+
sql << ')'
|
114
|
+
end
|
115
|
+
|
116
|
+
sql
|
117
|
+
end
|
118
|
+
|
119
|
+
def create_table_sql(name, generator, options)
|
120
|
+
_append_table_view_options_sql(super, options)
|
121
|
+
end
|
122
|
+
|
123
|
+
def create_table_as_sql(name, sql, options)
|
124
|
+
_append_table_view_options_sql(create_table_prefix_sql(name, options), options) << " AS #{sql}"
|
125
|
+
end
|
126
|
+
|
127
|
+
def create_view_sql(name, source, options)
|
128
|
+
if source.is_a?(Hash)
|
129
|
+
options = source
|
130
|
+
source = nil
|
131
|
+
end
|
132
|
+
|
133
|
+
sql = String.new
|
134
|
+
sql << create_view_sql_append_columns("CREATE #{'OR REPLACE 'if options[:replace]}#{'TEMPORARY ' if options[:temp]}VIEW#{' IF NOT EXISTS' if options[:if_not_exists]} #{quote_schema_table(name)}", options[:columns])
|
135
|
+
|
136
|
+
if source
|
137
|
+
source = source.sql if source.is_a?(Dataset)
|
138
|
+
sql << " AS " << source
|
139
|
+
end
|
140
|
+
|
141
|
+
_append_table_view_options_sql(sql, options)
|
142
|
+
end
|
143
|
+
|
144
|
+
def _append_table_view_options_sql(sql, options)
|
145
|
+
if options[:using]
|
146
|
+
sql << " USING " << options[:using].to_s
|
147
|
+
end
|
148
|
+
|
149
|
+
if options[:partitioned_by]
|
150
|
+
sql << " PARTITIONED BY "
|
151
|
+
_append_column_list_sql(sql, options[:partitioned_by])
|
152
|
+
end
|
153
|
+
|
154
|
+
if options[:clustered_by]
|
155
|
+
sql << " CLUSTERED BY "
|
156
|
+
_append_column_list_sql(sql, options[:clustered_by])
|
157
|
+
|
158
|
+
if options[:sorted_by]
|
159
|
+
sql << " SORTED BY "
|
160
|
+
_append_column_list_sql(sql, options[:sorted_by])
|
161
|
+
end
|
162
|
+
raise "Must specify :num_buckets when :clustered_by is used" unless options[:num_buckets]
|
163
|
+
sql << " INTO " << literal(options[:num_buckets]) << " BUCKETS"
|
164
|
+
end
|
165
|
+
|
166
|
+
if options[:options]
|
167
|
+
sql << ' OPTIONS ('
|
168
|
+
options[:options].each do |k, v|
|
169
|
+
sql << literal(k.to_s) << "=" << literal(v.to_s)
|
170
|
+
end
|
171
|
+
sql << ')'
|
172
|
+
end
|
173
|
+
|
174
|
+
sql
|
175
|
+
end
|
176
|
+
|
177
|
+
def _append_column_list_sql(sql, columns)
|
178
|
+
sql << '('
|
179
|
+
schema_utility_dataset.send(:identifier_list_append, sql, Array(columns))
|
180
|
+
sql << ')'
|
181
|
+
end
|
182
|
+
|
183
|
+
def drop_schema_sql(schema_name, opts)
|
184
|
+
sql = String.new
|
185
|
+
sql << 'DROP SCHEMA '
|
186
|
+
sql << 'IF EXISTS ' if opts[:if_exists]
|
187
|
+
sql << literal(schema_name)
|
188
|
+
sql << ' CASCADE' if opts[:cascade]
|
189
|
+
sql
|
190
|
+
end
|
191
|
+
|
192
|
+
def schema_parse_table(table, opts)
|
193
|
+
m = output_identifier_meth(opts[:dataset])
|
194
|
+
im = input_identifier_meth(opts[:dataset])
|
195
|
+
metadata_dataset.with_sql("DESCRIBE #{"#{im.call(opts[:schema])}." if opts[:schema]}#{im.call(table)}").map do |row|
|
196
|
+
[m.call(row[:col_name]), {:db_type=>row[:data_type], :type=>schema_column_type(row[:data_type])}]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def supports_create_or_replace_view?
|
201
|
+
true
|
202
|
+
end
|
203
|
+
|
204
|
+
def type_literal_generic_file(column)
|
205
|
+
'binary'
|
206
|
+
end
|
207
|
+
|
208
|
+
def type_literal_generic_float(column)
|
209
|
+
'float'
|
210
|
+
end
|
211
|
+
|
212
|
+
def type_literal_generic_string(column)
|
213
|
+
'string'
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
module DatasetMethods
|
218
|
+
include UnmodifiedIdentifiers::DatasetMethods
|
219
|
+
|
220
|
+
Dataset.def_sql_method(self, :select, [['if opts[:values]', %w'values'], ['else', %w'with select distinct columns from join where group having compounds order limit']])
|
221
|
+
|
222
|
+
def date_add_sql_append(sql, da)
|
223
|
+
expr = da.expr
|
224
|
+
cast_type = da.cast_type || Time
|
225
|
+
|
226
|
+
h = Hash.new(0)
|
227
|
+
da.interval.each do |k, v|
|
228
|
+
h[k] = v || 0
|
229
|
+
end
|
230
|
+
|
231
|
+
if h[:weeks]
|
232
|
+
h[:days] += h[:weeks] * 7
|
233
|
+
end
|
234
|
+
|
235
|
+
if h[:years] != 0 || h[:months] != 0
|
236
|
+
expr = Sequel.+(expr, Sequel.function(:make_ym_interval, h[:years], h[:months]))
|
237
|
+
end
|
238
|
+
|
239
|
+
if h[:days] != 0 || h[:hours] != 0 || h[:minutes] != 0 || h[:seconds] != 0
|
240
|
+
expr = Sequel.+(expr, Sequel.function(:make_dt_interval, h[:days], h[:hours], h[:minutes], h[:seconds]))
|
241
|
+
end
|
242
|
+
|
243
|
+
literal_append(sql, expr)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Emulate delete by selecting all rows except the ones being deleted
|
247
|
+
# into a new table, drop the current table, and rename the new
|
248
|
+
# table to the current table name.
|
249
|
+
#
|
250
|
+
# This is designed to minimize the changes to the tests, and is
|
251
|
+
# not recommended for production use.
|
252
|
+
def delete
|
253
|
+
_with_temp_table
|
254
|
+
end
|
255
|
+
|
256
|
+
def update(columns)
|
257
|
+
updated_cols = columns.keys
|
258
|
+
other_cols = db.from(first_source_table).columns - updated_cols
|
259
|
+
updated_vals = columns.values
|
260
|
+
|
261
|
+
_with_temp_table do |tmp_name|
|
262
|
+
db.from(tmp_name).insert([*updated_cols, *other_cols], select(*updated_vals, *other_cols))
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
private def _with_temp_table
|
267
|
+
n = count
|
268
|
+
table_name = first_source_table
|
269
|
+
tmp_name = literal(table_name).gsub('`', '') + "__sequel_delete_emulate"
|
270
|
+
db.create_table(tmp_name, :as=>select_all.invert)
|
271
|
+
yield tmp_name if defined?(yield)
|
272
|
+
db.drop_table(table_name)
|
273
|
+
db.rename_table(tmp_name, table_name)
|
274
|
+
n
|
275
|
+
end
|
276
|
+
|
277
|
+
protected def compound_clone(type, dataset, opts)
|
278
|
+
dataset = dataset.from_self if dataset.opts[:with]
|
279
|
+
super
|
280
|
+
end
|
281
|
+
|
282
|
+
def complex_expression_sql_append(sql, op, args)
|
283
|
+
case op
|
284
|
+
when :<<
|
285
|
+
literal_append(sql, Sequel.function(:shiftleft, *args))
|
286
|
+
when :>>
|
287
|
+
literal_append(sql, Sequel.function(:shiftright, *args))
|
288
|
+
when :~
|
289
|
+
literal_append(sql, Sequel.function(:regexp, *args))
|
290
|
+
when :'!~'
|
291
|
+
literal_append(sql, ~Sequel.function(:regexp, *args))
|
292
|
+
when :'~*'
|
293
|
+
literal_append(sql, Sequel.function(:regexp, Sequel.function(:lower, args[0]), Sequel.function(:lower, args[1])))
|
294
|
+
when :'!~*'
|
295
|
+
literal_append(sql, ~Sequel.function(:regexp, Sequel.function(:lower, args[0]), Sequel.function(:lower, args[1])))
|
296
|
+
else
|
297
|
+
super
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def multi_insert_sql_strategy
|
302
|
+
:values
|
303
|
+
end
|
304
|
+
|
305
|
+
def quoted_identifier_append(sql, name)
|
306
|
+
sql << '`' << name.to_s.gsub('`', '``') << '`'
|
307
|
+
end
|
308
|
+
|
309
|
+
def requires_sql_standard_datetimes?
|
310
|
+
true
|
311
|
+
end
|
312
|
+
|
313
|
+
def insert_supports_empty_values?
|
314
|
+
false
|
315
|
+
end
|
316
|
+
|
317
|
+
def literal_blob_append(sql, v)
|
318
|
+
sql << "to_binary('" << [v].pack("m*").gsub("\n", "") << "', 'base64')"
|
319
|
+
end
|
320
|
+
|
321
|
+
def literal_false
|
322
|
+
"false"
|
323
|
+
end
|
324
|
+
|
325
|
+
def literal_string_append(sql, v)
|
326
|
+
sql << "'" << v.gsub(/(['\\])/, '\\\\\1') << "'"
|
327
|
+
end
|
328
|
+
|
329
|
+
def literal_true
|
330
|
+
"true"
|
331
|
+
end
|
332
|
+
|
333
|
+
def supports_cte?(type=:select)
|
334
|
+
type == :select
|
335
|
+
end
|
336
|
+
|
337
|
+
def supports_cte_in_subqueries?
|
338
|
+
true
|
339
|
+
end
|
340
|
+
|
341
|
+
def supports_group_cube?
|
342
|
+
true
|
343
|
+
end
|
344
|
+
|
345
|
+
def supports_group_rollup?
|
346
|
+
true
|
347
|
+
end
|
348
|
+
|
349
|
+
def supports_grouping_sets?
|
350
|
+
true
|
351
|
+
end
|
352
|
+
|
353
|
+
def supports_regexp?
|
354
|
+
true
|
355
|
+
end
|
356
|
+
|
357
|
+
def supports_window_functions?
|
358
|
+
true
|
359
|
+
end
|
360
|
+
|
361
|
+
# Handle forward references in existing CTEs in the dataset by inserting this
|
362
|
+
# dataset before any dataset that would reference it.
|
363
|
+
def with(name, dataset, opts=OPTS)
|
364
|
+
opts = Hash[opts].merge!(:name=>name, :dataset=>dataset).freeze
|
365
|
+
references = ReferenceExtractor.references(dataset)
|
366
|
+
|
367
|
+
if with = @opts[:with]
|
368
|
+
with = with.dup
|
369
|
+
existing_references = @opts[:with_references]
|
370
|
+
|
371
|
+
if referencing_dataset = existing_references[literal(name)]
|
372
|
+
unless i = with.find_index{|o| o[:dataset].equal?(referencing_dataset)}
|
373
|
+
raise Sequel::Error, "internal error finding referencing dataset"
|
374
|
+
end
|
375
|
+
|
376
|
+
with.insert(i, opts)
|
377
|
+
|
378
|
+
# When not inserting dataset at the end, if both the new dataset and the
|
379
|
+
# dataset right after it refer to the same reference, keep the reference
|
380
|
+
# to the new dataset, so that that dataset is inserted before the new dataset
|
381
|
+
# dataset
|
382
|
+
existing_references = existing_references.reject do |k, v|
|
383
|
+
references[k] && v.equal?(referencing_dataset)
|
384
|
+
end
|
385
|
+
else
|
386
|
+
with << opts
|
387
|
+
end
|
388
|
+
|
389
|
+
# Assume we will insert the dataset at the end, so existing references have priority
|
390
|
+
references = references.merge(existing_references)
|
391
|
+
else
|
392
|
+
with = [opts]
|
393
|
+
end
|
394
|
+
|
395
|
+
clone(:with=>with.freeze, :with_references=>references.freeze)
|
396
|
+
end
|
397
|
+
|
398
|
+
private def select_values_sql(sql)
|
399
|
+
sql << 'VALUES '
|
400
|
+
expression_list_append(sql, opts[:values])
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
# ReferenceExtractor extracts references from datasets that will be used as CTEs.
|
405
|
+
class ReferenceExtractor < ASTTransformer
|
406
|
+
TABLE_IDENTIFIER_KEYS = [:from, :join].freeze
|
407
|
+
COLUMN_IDENTIFIER_KEYS = [:select, :where, :having, :order, :group, :compounds].freeze
|
408
|
+
|
409
|
+
# Returns a hash of literal string identifier keys referenced by the given
|
410
|
+
# dataset with the given dataset as the value for each key.
|
411
|
+
def self.references(dataset)
|
412
|
+
new(dataset).tap{|ext| ext.transform(dataset)}.references
|
413
|
+
end
|
414
|
+
|
415
|
+
attr_reader :references
|
416
|
+
|
417
|
+
def initialize(dataset)
|
418
|
+
@dataset = dataset
|
419
|
+
@references = {}
|
420
|
+
end
|
421
|
+
|
422
|
+
private
|
423
|
+
|
424
|
+
# Extract references from FROM/JOIN, where bare identifiers represent tables.
|
425
|
+
def table_identifier_extract(o)
|
426
|
+
case o
|
427
|
+
when String
|
428
|
+
@references[@dataset.literal(Sequel.identifier(o))] = @dataset
|
429
|
+
when Symbol, SQL::Identifier
|
430
|
+
@references[@dataset.literal(o)] = @dataset
|
431
|
+
when SQL::AliasedExpression
|
432
|
+
table_identifier_extract(o.expression)
|
433
|
+
when SQL::JoinOnClause
|
434
|
+
table_identifier_extract(o.table_expr)
|
435
|
+
v(o.on)
|
436
|
+
when SQL::JoinClause
|
437
|
+
table_identifier_extract(o.table_expr)
|
438
|
+
else
|
439
|
+
v(o)
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
# Extract references from datasets, where bare identifiers in most case represent columns,
|
444
|
+
# and only qualified identifiers include a table reference.
|
445
|
+
def v(o)
|
446
|
+
case o
|
447
|
+
when Sequel::Dataset
|
448
|
+
# Special case FROM/JOIN, because identifiers inside refer to tables and not columns
|
449
|
+
TABLE_IDENTIFIER_KEYS.each{|k| o.opts[k]&.each{|jc| table_identifier_extract(jc)}}
|
450
|
+
|
451
|
+
# Look in other keys that may have qualified references or subqueries
|
452
|
+
COLUMN_IDENTIFIER_KEYS.each{|k| v(o.opts[k])}
|
453
|
+
when SQL::QualifiedIdentifier
|
454
|
+
# If a qualified identifier has a qualified identifier as a key,
|
455
|
+
# such as schema.table.column, ignore it, because CTE identifiers shouldn't
|
456
|
+
# be schema qualified.
|
457
|
+
unless o.table.is_a?(SQL::QualifiedIdentifier)
|
458
|
+
@references[@dataset.literal(Sequel.identifier(o.table))] = @dataset
|
459
|
+
end
|
460
|
+
else
|
461
|
+
super
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
private_constant :ReferenceExtractor
|
466
|
+
end
|
467
|
+
end
|
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sequel-hexspace
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeremy Evans
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-04-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sequel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: hexspace
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.2.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.2.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5.7'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '5.7'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest-hooks
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest-global_expectations
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: |
|
98
|
+
This is a hexspace adapter for Sequel, designed to be used with Spark (not
|
99
|
+
Hive). You can use the hexspace:// protocol in the Sequel connection URL
|
100
|
+
to use this adapter.
|
101
|
+
email: code@jeremyevans.net
|
102
|
+
executables: []
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files:
|
105
|
+
- LICENSE
|
106
|
+
files:
|
107
|
+
- LICENSE
|
108
|
+
- README
|
109
|
+
- lib/sequel/adapters/hexspace.rb
|
110
|
+
- lib/sequel/adapters/shared/spark.rb
|
111
|
+
homepage: https://github.com/jeremyevans/sequel-hexspace.git
|
112
|
+
licenses:
|
113
|
+
- MIT
|
114
|
+
metadata: {}
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options:
|
117
|
+
- "--quiet"
|
118
|
+
- "--line-numbers"
|
119
|
+
- "--inline-source"
|
120
|
+
- "--title"
|
121
|
+
- 'sequel-hexspace: Sequel adapter for hexspace driver and Apache Spark database'
|
122
|
+
- "--main"
|
123
|
+
- README
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
requirements: []
|
137
|
+
rubygems_version: 3.4.6
|
138
|
+
signing_key:
|
139
|
+
specification_version: 4
|
140
|
+
summary: Sequel adapter for hexspace driver and Apache Spark database
|
141
|
+
test_files: []
|