sequel-hexspace 1.0.0 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2949511f39f19c48b5120965d8b236fe93215c07ee9cf987d7b80f81d649b81
4
- data.tar.gz: 68b65e86ccc1ad3cc1fef989eb1d79b7927f96c4f4b5a60abb9699c7b425f4a4
3
+ metadata.gz: b4421ed6430cdfa0ede39d8b4956c01de6e65317af4c9ab72238e06aa98d0e18
4
+ data.tar.gz: b01b91610baad3b154d7a44c193ad29e88fc940de34473158e60521af958161e
5
5
  SHA512:
6
- metadata.gz: 021db16089f80aee9a8398b54747265e6eedb59713b7fac36316d4623a324ce96036e532df0ac75171ec47a04ade882ee168c5c773e5487a7591567bfaab707c
7
- data.tar.gz: 2767b9661167bc70fb775513f4abcb0465a77cc19c16f9bdb94972acd30896a310c3daeb6d8a34cfdd089ccf6c8bd6558bc459a9ae2eb3415c7cfd35b5d7273a
6
+ metadata.gz: bfb4bbe3fddb1be528f0cdd382e78dfcf651edd36de086248742cc4d31d23dfa293b4619ba70acf6066681fe58cb6b0661373badd187c80fd8df54935513e3cd
7
+ data.tar.gz: 5a979505d337170a4d41bf96e135118284b34461a21611167856d0e95b79b63bf11eb71f4adae733c7ea579347d40091fba7ab038bc1efa5d10ccdf701801f5e
data/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ ## 2.0.0 (unreleased)
4
+
5
+ ### Breaking
6
+
7
+ - Declare `required_ruby_version >= 3.3`. Ruby 3.2 reached end of life in March
8
+ 2026 and is no longer tested. Installing on an older Ruby now fails with a
9
+ clear message rather than breaking at runtime. This is the only breaking
10
+ change, and it is what makes this release a major one.
11
+
12
+ ### Note on the gap since 1.0.0
13
+
14
+ 1.0.0 (April 2024) is the only version ever published; 1.0.1 was tagged in the
15
+ repository but never released. Everything below has therefore been unavailable
16
+ to users until now, and this is the first release to carry any of it. The list is
17
+ reconstructed from git history.
18
+
19
+ ### Added
20
+
21
+ - Shared Hexspace mock adapter support, so dependent projects can test without a
22
+ live Spark server.
23
+ - `EXTERNAL`, `LOCATION` and `LIKE` support in `CREATE TABLE`.
24
+ - Always-on SimpleCov coverage and RuboCop enforcement in the development setup.
25
+
26
+ ### Fixed
27
+
28
+ - Support thrift 0.24 by restoring `Thrift::Client#handle_exception` and
29
+ `#reply_seqid`, which 0.24.0 removed while hexspace's generated client still
30
+ calls them. Without this every Spark connection failed with a
31
+ `Sequel::DatabaseConnectionError`. thrift is constrained to `>= 0.18, < 0.25`.
32
+ - Silence a frozen-string-literal warning from thrift 0.22.0 on Ruby 3.4+.
33
+ - `Database#database_type` returns `:spark` rather than `:hexspace`.
34
+ - Ignore `CASCADE` in `drop_table_sql`, which Spark SQL does not accept.
35
+ - Route prepared `DELETE` statements through the emulated delete path.
36
+ - Date and timestamp casting and literalisation, including Ruby 4.0
37
+ compatibility.
data/LICENSE CHANGED
@@ -1,4 +1,5 @@
1
1
  Copyright (c) 2023 Jeremy Evans
2
+ Copyright (c) 2026 Outcomes Insights, Inc.
2
3
 
3
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
5
  of this software and associated documentation files (the "Software"), to
@@ -1,10 +1,77 @@
1
+ require_relative 'shared/hexspace'
1
2
  require 'hexspace'
2
- require_relative 'shared/spark'
3
+
4
+ # Backport upstream fix (THRIFT-5909, PR #3270) for thrift 0.22.0 on Ruby 3.4+.
5
+ # Thrift::Bytes.empty_byte_buffer calls force_encoding on string literals,
6
+ # which triggers "literal string will be frozen in the future" warnings.
7
+ # Remove this once thrift > 0.22.0 is released with the fix.
8
+ if defined?(Thrift::Bytes) && (spec = Gem.loaded_specs['thrift']) && spec.version < Gem::Version.new('0.23')
9
+ module Thrift
10
+ module Bytes
11
+ def self.empty_byte_buffer(size=nil)
12
+ if size&.positive?
13
+ "\0".b * size
14
+ else
15
+ ''.b
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ # Restore Thrift::Client#handle_exception and #reply_seqid for thrift >= 0.24.
23
+ #
24
+ # hexspace's generated Thrift client (lib/hexspace/tcli_service.rb) calls both,
25
+ # 21 times each. thrift 0.24.0 removed them, folding their work into the new
26
+ # #validate_message_begin -- so on thrift 0.24 every Spark connection dies in
27
+ # recv_OpenSession with NoMethodError, surfacing as
28
+ # Sequel::DatabaseConnectionError.
29
+ #
30
+ # Upstream will not fix this for us: hexspace's generated client is untouched
31
+ # since 2023-05-08, the latest release (0.3.0, Apr 2025) predates thrift 0.24,
32
+ # and no issue or PR there mentions thrift. Regenerating that client against
33
+ # 0.24 would drop support for thrift < 0.24, which is a maintainer's decision
34
+ # rather than ours to force.
35
+ #
36
+ # Both methods are restored verbatim from thrift 0.23.0 and are implemented
37
+ # purely in terms of primitives thrift 0.24 still provides and still uses in
38
+ # validate_message_begin. The dequeue accounting is unchanged: exactly one
39
+ # dequeue_pending_seqid per call on both the reply and the exception path.
40
+ #
41
+ # Note this keeps 0.23 semantics -- it does NOT add 0.24's stricter checks for
42
+ # invalid message type and wrong method name. Remove once hexspace regenerates
43
+ # its client against thrift >= 0.24.
44
+ # rubocop:disable-next Style/GuardClause, Naming/PredicateMethod
45
+ if defined?(Thrift::Client)
46
+ # Both methods below are copied verbatim from thrift 0.23.0 and deliberately
47
+ # not restyled: keeping them byte-comparable with upstream is what makes them
48
+ # auditable. reply_seqid also cannot be renamed to a predicate -- hexspace's
49
+ # generated client calls it by that exact name.
50
+ module Thrift
51
+ module Client
52
+ unless method_defined?(:handle_exception) || private_method_defined?(:handle_exception)
53
+ def handle_exception(mtype)
54
+ if mtype == MessageTypes::EXCEPTION
55
+ dequeue_pending_seqid
56
+ raise_application_exception
57
+ end
58
+ end
59
+ end
60
+
61
+ unless method_defined?(:reply_seqid) || private_method_defined?(:reply_seqid)
62
+ def reply_seqid(rseqid)
63
+ expected_seqid = dequeue_pending_seqid
64
+ !expected_seqid.nil? && rseqid == expected_seqid
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
3
70
 
4
71
  module Sequel
5
72
  module Hexspace
6
73
  class Database < Sequel::Database
7
- include Spark::DatabaseMethods
74
+ include DatabaseMethods
8
75
 
9
76
  set_adapter_scheme :hexspace
10
77
 
@@ -27,6 +94,17 @@ module Sequel
27
94
  conn.instance_variable_set(:@sequel_invalid, true)
28
95
  end
29
96
 
97
+ # Hexspace returns timestamp strings in UTC without an explicit offset.
98
+ # When no database timezone is configured, Sequel treats naive strings as
99
+ # local time, which shifts CURRENT_TIMESTAMP by the local UTC offset.
100
+ def to_application_timestamp(value)
101
+ if value.is_a?(String) && timezone.nil? && value !~ /(?:Z|[+-]\d{2}(?::?\d{2})?)\z/
102
+ Sequel.convert_timestamp(value, :utc)
103
+ else
104
+ super
105
+ end
106
+ end
107
+
30
108
  def execute(sql, opts=OPTS)
31
109
  synchronize(opts[:server]) do |conn|
32
110
  res = log_connection_yield(sql, conn){conn.execute(sql, result_object: true)}
@@ -51,7 +129,7 @@ module Sequel
51
129
  end
52
130
 
53
131
  class Dataset < Sequel::Dataset
54
- include Spark::DatasetMethods
132
+ include DatasetMethods
55
133
 
56
134
  def fetch_rows(sql)
57
135
  execute(sql) do |result|
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'spark'
4
+
5
+ module Sequel
6
+
7
+ module Hexspace
8
+
9
+ module DatabaseMethods
10
+
11
+ include Sequel::Spark::DatabaseMethods
12
+
13
+ end
14
+
15
+ module DatasetMethods
16
+
17
+ include Sequel::Spark::DatasetMethods
18
+
19
+ end
20
+
21
+ end
22
+
23
+ Sequel::Database.set_shared_adapter_scheme(:hexspace, Sequel::Hexspace)
24
+
25
+ end
@@ -28,7 +28,7 @@ module Sequel
28
28
  # ignoring the primary key setting.
29
29
  {:type=>Integer}
30
30
  end
31
-
31
+
32
32
  def supports_create_table_if_not_exists?
33
33
  true
34
34
  end
@@ -116,8 +116,23 @@ module Sequel
116
116
  sql
117
117
  end
118
118
 
119
+ def create_table_prefix_sql(name, options)
120
+ sql = String.new
121
+ sql << 'CREATE '
122
+ sql << 'EXTERNAL ' if options[:external]
123
+ sql << 'TEMPORARY ' if options[:temp]
124
+ sql << 'TABLE '
125
+ sql << 'IF NOT EXISTS ' if options[:if_not_exists]
126
+ sql << quote_schema_table(name)
127
+ sql
128
+ end
129
+
119
130
  def create_table_sql(name, generator, options)
120
- _append_table_view_options_sql(super, options)
131
+ if options[:like] || (options[:using] && generator.columns.empty?)
132
+ _append_table_view_options_sql(create_table_prefix_sql(name, options), options)
133
+ else
134
+ _append_table_view_options_sql(super, options)
135
+ end
121
136
  end
122
137
 
123
138
  def create_table_as_sql(name, sql, options)
@@ -142,10 +157,18 @@ module Sequel
142
157
  end
143
158
 
144
159
  def _append_table_view_options_sql(sql, options)
160
+ if like = options[:like]
161
+ sql << " LIKE " << literal(like)
162
+ end
163
+
145
164
  if options[:using]
146
165
  sql << " USING " << options[:using].to_s
147
166
  end
148
167
 
168
+ if location = options[:location]
169
+ sql << " LOCATION " << literal(location)
170
+ end
171
+
149
172
  if options[:partitioned_by]
150
173
  sql << " PARTITIONED BY "
151
174
  _append_column_list_sql(sql, options[:partitioned_by])
@@ -175,7 +198,7 @@ module Sequel
175
198
  end
176
199
 
177
200
  def _append_column_list_sql(sql, columns)
178
- sql << '('
201
+ sql << '('
179
202
  schema_utility_dataset.send(:identifier_list_append, sql, Array(columns))
180
203
  sql << ')'
181
204
  end
@@ -189,6 +212,12 @@ module Sequel
189
212
  sql
190
213
  end
191
214
 
215
+ # Spark SQL does not support CASCADE on DROP TABLE.
216
+ # Support :purge option, ignore :cascade.
217
+ def drop_table_sql(name, options)
218
+ "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_schema_table(name)}#{' PURGE' if options[:purge]}"
219
+ end
220
+
192
221
  def schema_parse_table(table, opts)
193
222
  m = output_identifier_meth(opts[:dataset])
194
223
  im = input_identifier_meth(opts[:dataset])
@@ -243,6 +272,17 @@ module Sequel
243
272
  literal_append(sql, expr)
244
273
  end
245
274
 
275
+ # Route prepared statement / bound variable deletes through the
276
+ # emulated delete path, since Spark does not support native DELETE.
277
+ def call(type, bind_variables=OPTS, *values, &)
278
+ if type == :delete
279
+ ps = to_prepared_statement(type, values, :extend=>send(:bound_variable_modules))
280
+ ps.bind(bind_variables).delete
281
+ else
282
+ super
283
+ end
284
+ end
285
+
246
286
  # Emulate delete by selecting all rows except the ones being deleted
247
287
  # into a new table, drop the current table, and rename the new
248
288
  # table to the current table name.
@@ -310,7 +350,7 @@ module Sequel
310
350
  true
311
351
  end
312
352
 
313
- def insert_supports_empty_values?
353
+ def insert_supports_empty_values?
314
354
  false
315
355
  end
316
356
 
@@ -318,6 +358,24 @@ module Sequel
318
358
  sql << "to_binary('" << [v].pack("m*").gsub("\n", "") << "', 'base64')"
319
359
  end
320
360
 
361
+ # Spark requires DATE 'YYYY-MM-DD' syntax instead of plain quoted strings
362
+ def literal_date_append(sql, v)
363
+ sql << "DATE "
364
+ super
365
+ end
366
+
367
+ # Spark requires TIMESTAMP 'YYYY-MM-DD HH:MM:SS' syntax instead of plain quoted strings
368
+ def literal_time_append(sql, v)
369
+ sql << "TIMESTAMP "
370
+ super
371
+ end
372
+
373
+ # Spark requires TIMESTAMP 'YYYY-MM-DD HH:MM:SS' syntax instead of plain quoted strings
374
+ def literal_datetime_append(sql, v)
375
+ sql << "TIMESTAMP "
376
+ super
377
+ end
378
+
321
379
  def literal_false
322
380
  "false"
323
381
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel-hexspace
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
- autorequire:
8
+ - Ryan Duryea
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-03 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -38,6 +38,26 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.2.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: thrift
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0.18'
48
+ - - "<"
49
+ - !ruby/object:Gem::Version
50
+ version: '0.25'
51
+ type: :runtime
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0.18'
58
+ - - "<"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.25'
41
61
  - !ruby/object:Gem::Dependency
42
62
  name: rake
43
63
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +78,14 @@ dependencies:
58
78
  requirements:
59
79
  - - "~>"
60
80
  - !ruby/object:Gem::Version
61
- version: '5.7'
81
+ version: '6.0'
62
82
  type: :development
63
83
  prerelease: false
64
84
  version_requirements: !ruby/object:Gem::Requirement
65
85
  requirements:
66
86
  - - "~>"
67
87
  - !ruby/object:Gem::Version
68
- version: '5.7'
88
+ version: '6.0'
69
89
  - !ruby/object:Gem::Dependency
70
90
  name: minitest-hooks
71
91
  requirement: !ruby/object:Gem::Requirement
@@ -94,25 +114,40 @@ dependencies:
94
114
  - - ">="
95
115
  - !ruby/object:Gem::Version
96
116
  version: '0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: simplecov
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '1.0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '1.0'
97
131
  description: |
98
132
  This is a hexspace adapter for Sequel, designed to be used with Spark (not
99
133
  Hive). You can use the hexspace:// protocol in the Sequel connection URL
100
134
  to use this adapter.
101
- email: code@jeremyevans.net
135
+ email: aguynamedryan@gmail.com
102
136
  executables: []
103
137
  extensions: []
104
138
  extra_rdoc_files:
105
139
  - LICENSE
106
140
  files:
141
+ - CHANGELOG.md
107
142
  - LICENSE
108
143
  - README
109
144
  - lib/sequel/adapters/hexspace.rb
145
+ - lib/sequel/adapters/shared/hexspace.rb
110
146
  - lib/sequel/adapters/shared/spark.rb
111
- homepage: https://github.com/jeremyevans/sequel-hexspace.git
147
+ homepage: https://github.com/outcomesinsights/sequel-hexspace
112
148
  licenses:
113
149
  - MIT
114
150
  metadata: {}
115
- post_install_message:
116
151
  rdoc_options:
117
152
  - "--quiet"
118
153
  - "--line-numbers"
@@ -127,15 +162,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
127
162
  requirements:
128
163
  - - ">="
129
164
  - !ruby/object:Gem::Version
130
- version: '0'
165
+ version: '3.3'
131
166
  required_rubygems_version: !ruby/object:Gem::Requirement
132
167
  requirements:
133
168
  - - ">="
134
169
  - !ruby/object:Gem::Version
135
170
  version: '0'
136
171
  requirements: []
137
- rubygems_version: 3.4.6
138
- signing_key:
172
+ rubygems_version: 4.0.20
139
173
  specification_version: 4
140
174
  summary: Sequel adapter for hexspace driver and Apache Spark database
141
175
  test_files: []