database_consistency 3.0.11 → 3.0.12
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 +67 -17
- 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: cf61dd8acf18a8e18b559df07d481e8503576cd228ec74190aa54b2a5b11e200
|
|
4
|
+
data.tar.gz: 4c2ffed1815099f0c2102043b976a85236017b6c4d92287741a48459824a047c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b04e984adeec1807d37f83b6826d0e4499e95c1bc8b3042b9a078e348de499d1df1d1b2437972eb2899a6716da8f4e498d946629570e761ec3ef59969254ffac
|
|
7
|
+
data.tar.gz: c100ad7001fe64ce78b1fed323b0e1c389b4bacfb899a26a215731ca4327f6b026828063ea48ac3a494958c8b59916a94b88577c16d342c6205b69d843b11769
|
|
@@ -151,7 +151,7 @@ module DatabaseConsistency
|
|
|
151
151
|
where_part = sql.split(/\bWHERE\b/i, 2).last
|
|
152
152
|
return nil unless where_part
|
|
153
153
|
|
|
154
|
-
normalize_condition_sql(where_part.gsub("#{model.quoted_table_name}.", '')
|
|
154
|
+
normalize_condition_sql(where_part.gsub("#{model.quoted_table_name}.", ''))
|
|
155
155
|
rescue StandardError
|
|
156
156
|
nil
|
|
157
157
|
end
|
|
@@ -188,31 +188,68 @@ module DatabaseConsistency
|
|
|
188
188
|
validator_where.casecmp?(normalized_where)
|
|
189
189
|
end
|
|
190
190
|
|
|
191
|
+
# Prefix used to mask string literals while regex normalization runs, so
|
|
192
|
+
# patterns that strip casts or unwrap parentheses never see the inside of a
|
|
193
|
+
# literal value. Angle brackets are used so the placeholder cannot be mistaken
|
|
194
|
+
# for a column name by the boolean-predicate normalizer.
|
|
195
|
+
LITERAL_PLACEHOLDER = '__DATABASE_CONSISTENCY_LITERAL<%<index>d>__'
|
|
196
|
+
|
|
191
197
|
# Normalizes SQL predicates into a canonical form so semantically equivalent
|
|
192
198
|
# Rails validators and database partial indexes can be compared safely.
|
|
193
199
|
def normalize_condition_sql(sql)
|
|
194
|
-
sql
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
200
|
+
masked_sql, literals = sql.to_s
|
|
201
|
+
.then { |value| strip_outer_parentheses(value) }
|
|
202
|
+
.then { |value| normalize_sql_pre_mask(value) }
|
|
203
|
+
.then { |value| mask_condition_literals(value) }
|
|
204
|
+
|
|
205
|
+
normalize_masked_condition_sql(masked_sql, literals)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Finishes normalization after string literals have been masked: runs the
|
|
209
|
+
# regex-based transforms that must not see inside literals, restores the
|
|
210
|
+
# literals, then applies the final clean-ups.
|
|
211
|
+
def normalize_masked_condition_sql(masked_sql, literals)
|
|
212
|
+
masked_sql
|
|
213
|
+
.then { |value| normalize_sql_post_mask(value) }
|
|
198
214
|
.then { |value| normalize_boolean_predicates(value) }
|
|
199
215
|
.then { |value| normalize_array_any_predicates(value) }
|
|
216
|
+
.then { |value| unmask_condition_literals(value, literals) }
|
|
200
217
|
.then { |value| normalize_negated_blank_or_nil_predicates(value) }
|
|
201
218
|
.then { |value| sort_and_clauses(value) }
|
|
219
|
+
.then { |value| value.gsub(/\s+/, ' ').strip }
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Masks non-empty string literals so later regexes cannot rewrite their
|
|
223
|
+
# contents. Empty literals are left untouched because negated-blank
|
|
224
|
+
# normalization relies on them.
|
|
225
|
+
def mask_condition_literals(sql)
|
|
226
|
+
literals = []
|
|
227
|
+
masked_sql = sql.gsub(/'(?:[^']|'')*'/) do |match|
|
|
228
|
+
if match == "''"
|
|
229
|
+
match
|
|
230
|
+
else
|
|
231
|
+
literals << match
|
|
232
|
+
format(LITERAL_PLACEHOLDER, index: literals.length - 1)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
[masked_sql, literals]
|
|
202
236
|
end
|
|
203
237
|
|
|
204
|
-
#
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
238
|
+
# Restores literals in the order they were masked. Uses a block replacement
|
|
239
|
+
# so backslashes inside the literal are not interpreted as regexp backrefs.
|
|
240
|
+
def unmask_condition_literals(sql, literals)
|
|
241
|
+
literals.each_with_index do |literal, index|
|
|
242
|
+
sql = sql.sub(format(LITERAL_PLACEHOLDER, index: index)) { literal }
|
|
243
|
+
end
|
|
244
|
+
sql
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Normalizations that must run before string literals are masked.
|
|
248
|
+
def normalize_sql_pre_mask(sql)
|
|
249
|
+
normalized_sql = sql.dup
|
|
211
250
|
# `/\bTRUE\b/i` and `/\bFALSE\b/i` normalize boolean literals to `1` / `0`
|
|
212
251
|
# so they match SQL generated by Active Record on some adapters.
|
|
213
252
|
normalized_sql = normalized_sql.gsub(/\bTRUE\b/i, '1').gsub(/\bFALSE\b/i, '0')
|
|
214
|
-
# `/\s*<>\s*/` rewrites the SQL inequality operator `<>` to `!=`.
|
|
215
|
-
normalized_sql = normalized_sql.gsub(/\s*<>\s*/, ' != ')
|
|
216
253
|
# `/\bIS\s+NOT\s+NULL\b/i` normalizes `IS NOT NULL` spacing and casing.
|
|
217
254
|
normalized_sql = normalized_sql.gsub(/\bIS\s+NOT\s+NULL\b/i, ' IS NOT NULL')
|
|
218
255
|
# `/\bIS\s+NULL\b/i` normalizes `IS NULL` spacing and casing.
|
|
@@ -220,9 +257,22 @@ module DatabaseConsistency
|
|
|
220
257
|
# `/ = 't'/` and `/ = 'f'/` normalize PostgreSQL boolean literals stored
|
|
221
258
|
# as `'t'` / `'f'` inside comparisons.
|
|
222
259
|
normalized_sql = normalized_sql.gsub(/ = 't'/, ' = 1').gsub(/ = 'f'/, ' = 0')
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
260
|
+
normalized_sql.gsub(/\s+/, ' ').strip
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Normalizations that run while string literals are masked.
|
|
264
|
+
def normalize_sql_post_mask(sql)
|
|
265
|
+
# Strips quoted identifiers (double quotes on PostgreSQL/SQLite,
|
|
266
|
+
# backticks on MySQL) so the same column normalizes across adapters.
|
|
267
|
+
normalized_sql = sql.gsub(/["`]/, '')
|
|
268
|
+
# `/::\w+/` removes PostgreSQL casts like `column::text`.
|
|
269
|
+
normalized_sql = normalized_sql.gsub(/::\w+/, '')
|
|
270
|
+
# `/\(([a-z_][\w.]*)\)/i` unwraps a bare identifier surrounded by
|
|
271
|
+
# parentheses, e.g. `(internal_name)` -> `internal_name`.
|
|
272
|
+
normalized_sql = normalized_sql.gsub(/\(([a-z_][\w.]*)\)/i, '\1')
|
|
273
|
+
# `/\s*<>\s*/` rewrites the SQL inequality operator `<>` to `!=`.
|
|
274
|
+
normalized_sql = normalized_sql.gsub(/\s*<>\s*/, ' != ')
|
|
275
|
+
normalized_sql.gsub(/\s+/, ' ').strip
|
|
226
276
|
end
|
|
227
277
|
|
|
228
278
|
# Repeatedly removes one wrapping layer of parentheses when the whole SQL
|
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.12
|
|
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-
|
|
11
|
+
date: 2026-09-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|