database_consistency 3.0.13 → 3.0.14
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 +4 -4
- data/lib/database_consistency/helper.rb +53 -15
- data/lib/database_consistency/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c0599ad1c39057b6a4ec4bb2c60938454a92296906258cda66b5b9f47081447e
|
|
4
|
+
data.tar.gz: 77d57bb76d65535e3dea3dfa88d8b5efcce14a230682ee472fd26a1f605c619a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9cffcfa84c2731c584788352910c712f9947cc4fcdc73f24bb68128889f6ec0674accdef8d2390d113fb4f16c7e37e5c859dd09fe649d75b2be04b4964fc10c3
|
|
7
|
+
data.tar.gz: 3fc92238031f7e9e3611179a5fcad66b8105374eaa799416ae426248a9f88b7baafd61642e98a2eb03d3db0d591cf241d7515388f309b058dca2c20fd04d783d
|
|
@@ -199,6 +199,22 @@ module DatabaseConsistency
|
|
|
199
199
|
# rather than the end of it.
|
|
200
200
|
CONDITION_LITERAL = /'(?:[^']|'')*'/.freeze
|
|
201
201
|
|
|
202
|
+
# Matches a masked literal, so steps that run on masked SQL can step over
|
|
203
|
+
# the `<` and `>` in the placeholder.
|
|
204
|
+
MASKED_LITERAL = Regexp.new(
|
|
205
|
+
Regexp.escape(LITERAL_PLACEHOLDER).sub('%<index>d') { '\d+' }
|
|
206
|
+
).freeze
|
|
207
|
+
|
|
208
|
+
# Matches an operator together with whatever spaces were written around it.
|
|
209
|
+
# `+` and `-` are left out: either can be a sign as well as an operator, and
|
|
210
|
+
# telling the two apart takes a parser. `->` and `->>` are the exception,
|
|
211
|
+
# since neither can be read as a sign.
|
|
212
|
+
CONDITION_OPERATOR = %r{\s*(->>?|[<>=!~@#%^&|?*/]+)\s*}.freeze
|
|
213
|
+
|
|
214
|
+
# Matches either of the two, so the spacing step can find operators while
|
|
215
|
+
# passing over the placeholders.
|
|
216
|
+
MASKED_LITERAL_OR_OPERATOR = Regexp.union(MASKED_LITERAL, CONDITION_OPERATOR).freeze
|
|
217
|
+
|
|
202
218
|
# Matches a number PostgreSQL had to quote in order to coerce it, together
|
|
203
219
|
# with the cast that says it is a number rather than a string. `::text` is
|
|
204
220
|
# deliberately absent from the list so a genuine string keeps its quotes.
|
|
@@ -244,7 +260,9 @@ module DatabaseConsistency
|
|
|
244
260
|
IN_LIST_OPENING = /(?<!\bIN\s)/i.freeze
|
|
245
261
|
|
|
246
262
|
# Matches a bare identifier wrapped in parentheses, e.g. `(internal_name)`.
|
|
247
|
-
|
|
263
|
+
# The lookbehind keeps the argument list of a call such as `lower(name)`
|
|
264
|
+
# intact.
|
|
265
|
+
WRAPPED_IDENTIFIER = /(?<![\w.])#{IN_LIST_OPENING}\(([a-z_][\w.]*)\)/i.freeze
|
|
248
266
|
|
|
249
267
|
# Matches a parenthesized numeric literal, e.g. `(0)` or `(0.001)`, which is
|
|
250
268
|
# what a cast such as `(0)::numeric` leaves behind once the cast is gone.
|
|
@@ -262,20 +280,26 @@ module DatabaseConsistency
|
|
|
262
280
|
|
|
263
281
|
# Matches a bare negated boolean predicate such as `NOT archived`, in the
|
|
264
282
|
# three places one can stand: at the start of an expression, after `AND` or
|
|
265
|
-
# `OR`, or after an opening parenthesis.
|
|
283
|
+
# `OR`, or after an opening parenthesis. The whitespace before whatever
|
|
284
|
+
# follows sits inside the lookahead, so the match leaves it in place instead
|
|
285
|
+
# of consuming it and fusing the next `AND` / `OR` to the rewritten
|
|
286
|
+
# predicate. The lookbehind keeps a call's own parenthesis out of the
|
|
287
|
+
# boolean positions, so the argument of `lower(...)` is not read as a
|
|
288
|
+
# predicate of its own.
|
|
266
289
|
NEGATED_BOOLEAN_PREDICATE = /
|
|
267
|
-
(^ | (?: \bAND\b | \bOR\b | \( ))
|
|
268
|
-
\s* NOT \s+ ([a-z_][\w.]*)
|
|
269
|
-
(?= $ |
|
|
290
|
+
(^ | (?: \bAND\b | \bOR\b | (?<![\w.]) \( ))
|
|
291
|
+
\s* NOT \s+ ([a-z_][\w.]*)
|
|
292
|
+
(?= \s* (?: $ | \bAND\b | \bOR\b | \) ))
|
|
270
293
|
/xi.freeze
|
|
271
294
|
|
|
272
295
|
# Matches a bare boolean predicate such as `most_recent` in those same three
|
|
273
|
-
# places. It runs after the negated
|
|
274
|
-
# gone and cannot be read as the
|
|
296
|
+
# places, with the same lookahead and lookbehind. It runs after the negated
|
|
297
|
+
# form so that `NOT archived` is already gone and cannot be read as the
|
|
298
|
+
# predicate `archived`.
|
|
275
299
|
BARE_BOOLEAN_PREDICATE = /
|
|
276
|
-
(^ | (?: \bAND\b | \bOR\b | \( ))
|
|
277
|
-
\s* ([a-z_][\w.]*)
|
|
278
|
-
(?= $ |
|
|
300
|
+
(^ | (?: \bAND\b | \bOR\b | (?<![\w.]) \( ))
|
|
301
|
+
\s* ([a-z_][\w.]*)
|
|
302
|
+
(?= \s* (?: $ | \bAND\b | \bOR\b | \) ))
|
|
279
303
|
/xi.freeze
|
|
280
304
|
|
|
281
305
|
# Matches `column = ANY (ARRAY[...])` or `column != ALL ((ARRAY[...]))`,
|
|
@@ -442,11 +466,23 @@ module DatabaseConsistency
|
|
|
442
466
|
"#{sign}#{expanded}".sub(/(\.\d*?)0+\z/, '\1').chomp('.')
|
|
443
467
|
end
|
|
444
468
|
|
|
469
|
+
# Gives every operator a space either side, every comma a space after it
|
|
470
|
+
# and no parenthesis a space on its inside, which is how PostgreSQL writes
|
|
471
|
+
# an indexdef however the index was typed.
|
|
472
|
+
def normalize_operator_spacing(sql)
|
|
473
|
+
spaced_sql = sql.gsub(MASKED_LITERAL_OR_OPERATOR) do |match|
|
|
474
|
+
match.match?(MASKED_LITERAL) ? match : " #{match.strip} "
|
|
475
|
+
end
|
|
476
|
+
spaced_sql = spaced_sql.gsub(/\s*,\s*/, ', ')
|
|
477
|
+
spaced_sql.gsub(/\(\s+/, '(').gsub(/\s+\)/, ')')
|
|
478
|
+
end
|
|
479
|
+
|
|
445
480
|
# Rewrites the spellings that differ between adapters, or between what an
|
|
446
481
|
# adapter stores and what Active Record writes: quoted identifiers, casts,
|
|
447
|
-
# exponent notation, the spacing of an `IN` list,
|
|
448
|
-
# adds around a cast operand and the `<>`
|
|
449
|
-
#
|
|
482
|
+
# exponent notation, the spacing of an `IN` list, of operators and of
|
|
483
|
+
# commas, the parentheses PostgreSQL adds around a cast operand and the `<>`
|
|
484
|
+
# it writes for inequality. Literals are masked throughout, so none of it
|
|
485
|
+
# reaches the inside of a value.
|
|
450
486
|
def normalize_adapter_syntax(sql)
|
|
451
487
|
# Strips quoted identifiers (double quotes on PostgreSQL/SQLite,
|
|
452
488
|
# backticks on MySQL) so the same column normalizes across adapters.
|
|
@@ -457,9 +493,11 @@ module DatabaseConsistency
|
|
|
457
493
|
# reach the same string and the list is recognisable to the unwrappers
|
|
458
494
|
# below. `\b` keeps a call such as `min(1)` out of it.
|
|
459
495
|
normalized_sql = normalized_sql.gsub(/\bIN\s*\(/i, 'IN (')
|
|
496
|
+
normalized_sql = normalize_operator_spacing(normalized_sql)
|
|
460
497
|
normalized_sql = unwrap_redundant_parentheses(normalized_sql)
|
|
461
|
-
#
|
|
462
|
-
|
|
498
|
+
# Rewrites the SQL inequality operator `<>` to `!=`; the spacing step
|
|
499
|
+
# has already given it a space either side.
|
|
500
|
+
normalized_sql = normalized_sql.gsub('<>', '!=')
|
|
463
501
|
normalized_sql.gsub(/\s+/, ' ').strip
|
|
464
502
|
end
|
|
465
503
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: database_consistency
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.14
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evgeniy Demin
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|