gitlab-pg_query 1.3.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/CHANGELOG.md +321 -0
- data/LICENSE +28 -0
- data/README.md +180 -0
- data/Rakefile +23 -0
- data/ext/pg_query/extconf.rb +46 -0
- data/ext/pg_query/pg_query_ruby.c +129 -0
- data/ext/pg_query/pg_query_ruby.h +10 -0
- data/ext/pg_query/pg_query_ruby.sym +1 -0
- data/lib/pg_query.rb +16 -0
- data/lib/pg_query/deep_dup.rb +16 -0
- data/lib/pg_query/deparse.rb +1588 -0
- data/lib/pg_query/deparse/alter_table.rb +42 -0
- data/lib/pg_query/deparse/interval.rb +105 -0
- data/lib/pg_query/deparse/keywords.rb +159 -0
- data/lib/pg_query/deparse/rename.rb +41 -0
- data/lib/pg_query/filter_columns.rb +107 -0
- data/lib/pg_query/fingerprint.rb +115 -0
- data/lib/pg_query/legacy_parsetree.rb +109 -0
- data/lib/pg_query/node_types.rb +296 -0
- data/lib/pg_query/param_refs.rb +45 -0
- data/lib/pg_query/parse.rb +247 -0
- data/lib/pg_query/parse_error.rb +9 -0
- data/lib/pg_query/treewalker.rb +53 -0
- data/lib/pg_query/truncate.rb +60 -0
- data/lib/pg_query/version.rb +3 -0
- metadata +130 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
class PgQuery
|
2
|
+
def param_refs # rubocop:disable Metrics/CyclomaticComplexity
|
3
|
+
results = []
|
4
|
+
|
5
|
+
treewalker! @tree do |_, _, v|
|
6
|
+
next unless v.is_a?(Hash)
|
7
|
+
|
8
|
+
if v[PARAM_REF]
|
9
|
+
results << { 'location' => v[PARAM_REF]['location'],
|
10
|
+
'length' => param_ref_length(v[PARAM_REF]) }
|
11
|
+
elsif v[TYPE_CAST]
|
12
|
+
next unless v[TYPE_CAST]['arg'] && v[TYPE_CAST]['typeName']
|
13
|
+
|
14
|
+
p = v[TYPE_CAST]['arg'].delete(PARAM_REF)
|
15
|
+
t = v[TYPE_CAST]['typeName'].delete(TYPE_NAME)
|
16
|
+
next unless p && t
|
17
|
+
|
18
|
+
location = p['location']
|
19
|
+
typeloc = t['location']
|
20
|
+
typename = t['names']
|
21
|
+
length = param_ref_length(p)
|
22
|
+
|
23
|
+
if typeloc < location
|
24
|
+
length += location - typeloc
|
25
|
+
location = typeloc
|
26
|
+
end
|
27
|
+
|
28
|
+
results << { 'location' => location, 'length' => length, 'typename' => typename }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
results.sort_by! { |r| r['location'] }
|
33
|
+
results
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def param_ref_length(paramref_node)
|
39
|
+
if paramref_node['number'] == 0 # rubocop:disable Style/NumericPredicate
|
40
|
+
1 # Actually a ? replacement character
|
41
|
+
else
|
42
|
+
('$' + paramref_node['number'].to_s).size
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class PgQuery
|
4
|
+
def self.parse(query)
|
5
|
+
tree, stderr = _raw_parse(query)
|
6
|
+
|
7
|
+
begin
|
8
|
+
tree = JSON.parse(tree, max_nesting: 1000)
|
9
|
+
rescue JSON::ParserError
|
10
|
+
raise ParseError.new('Failed to parse JSON', __FILE__, __LINE__, -1)
|
11
|
+
end
|
12
|
+
|
13
|
+
warnings = []
|
14
|
+
stderr.each_line do |line|
|
15
|
+
next unless line[/^WARNING/]
|
16
|
+
warnings << line.strip
|
17
|
+
end
|
18
|
+
|
19
|
+
PgQuery.new(query, tree, warnings)
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :query
|
23
|
+
attr_reader :tree
|
24
|
+
attr_reader :warnings
|
25
|
+
|
26
|
+
def initialize(query, tree, warnings = [])
|
27
|
+
@query = query
|
28
|
+
@tree = tree
|
29
|
+
@warnings = warnings
|
30
|
+
@tables = nil
|
31
|
+
@aliases = nil
|
32
|
+
@cte_names = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def tables
|
36
|
+
tables_with_types.map { |t| t[:table] }
|
37
|
+
end
|
38
|
+
|
39
|
+
def select_tables
|
40
|
+
tables_with_types.select { |t| t[:type] == :select }.map { |t| t[:table] }
|
41
|
+
end
|
42
|
+
|
43
|
+
def dml_tables
|
44
|
+
tables_with_types.select { |t| t[:type] == :dml }.map { |t| t[:table] }
|
45
|
+
end
|
46
|
+
|
47
|
+
def ddl_tables
|
48
|
+
tables_with_types.select { |t| t[:type] == :ddl }.map { |t| t[:table] }
|
49
|
+
end
|
50
|
+
|
51
|
+
def cte_names
|
52
|
+
load_tables_and_aliases! if @cte_names.nil?
|
53
|
+
@cte_names
|
54
|
+
end
|
55
|
+
|
56
|
+
def aliases
|
57
|
+
load_tables_and_aliases! if @aliases.nil?
|
58
|
+
@aliases
|
59
|
+
end
|
60
|
+
|
61
|
+
def tables_with_types
|
62
|
+
load_tables_and_aliases! if @tables.nil?
|
63
|
+
@tables
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def load_tables_and_aliases! # rubocop:disable Metrics/CyclomaticComplexity
|
69
|
+
@tables = [] # types: select, dml, ddl
|
70
|
+
@cte_names = []
|
71
|
+
@aliases = {}
|
72
|
+
|
73
|
+
statements = @tree.dup
|
74
|
+
from_clause_items = [] # types: select, dml, ddl
|
75
|
+
subselect_items = []
|
76
|
+
|
77
|
+
loop do
|
78
|
+
statement = statements.shift
|
79
|
+
if statement
|
80
|
+
case statement.keys[0]
|
81
|
+
when RAW_STMT
|
82
|
+
statements << statement[RAW_STMT][STMT_FIELD]
|
83
|
+
# The following statement types do not modify tables and are added to from_clause_items
|
84
|
+
# (and subsequently @tables)
|
85
|
+
when SELECT_STMT
|
86
|
+
case statement[SELECT_STMT]['op']
|
87
|
+
when 0
|
88
|
+
(statement[SELECT_STMT][FROM_CLAUSE_FIELD] || []).each do |item|
|
89
|
+
if item[RANGE_SUBSELECT]
|
90
|
+
statements << item[RANGE_SUBSELECT]['subquery']
|
91
|
+
else
|
92
|
+
from_clause_items << { item: item, type: :select }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
when 1
|
96
|
+
statements << statement[SELECT_STMT]['larg'] if statement[SELECT_STMT]['larg']
|
97
|
+
statements << statement[SELECT_STMT]['rarg'] if statement[SELECT_STMT]['rarg']
|
98
|
+
end
|
99
|
+
|
100
|
+
if (with_clause = statement[SELECT_STMT]['withClause'])
|
101
|
+
cte_statements, cte_names = statements_and_cte_names_for_with_clause(with_clause)
|
102
|
+
@cte_names.concat(cte_names)
|
103
|
+
statements.concat(cte_statements)
|
104
|
+
end
|
105
|
+
# The following statements modify the contents of a table
|
106
|
+
when INSERT_STMT, UPDATE_STMT, DELETE_STMT
|
107
|
+
value = statement.values[0]
|
108
|
+
from_clause_items << { item: value['relation'], type: :dml }
|
109
|
+
statements << value['selectStmt'] if value.key?('selectStmt')
|
110
|
+
statements << value['withClause'] if value.key?('withClause')
|
111
|
+
|
112
|
+
if (with_clause = value['withClause'])
|
113
|
+
cte_statements, cte_names = statements_and_cte_names_for_with_clause(with_clause)
|
114
|
+
@cte_names.concat(cte_names)
|
115
|
+
statements.concat(cte_statements)
|
116
|
+
end
|
117
|
+
when COPY_STMT
|
118
|
+
from_clause_items << { item: statement.values[0]['relation'], type: :dml } if statement.values[0]['relation']
|
119
|
+
statements << statement.values[0]['query']
|
120
|
+
# The following statement types are DDL (changing table structure)
|
121
|
+
when ALTER_TABLE_STMT, CREATE_STMT
|
122
|
+
from_clause_items << { item: statement.values[0]['relation'], type: :ddl }
|
123
|
+
when CREATE_TABLE_AS_STMT
|
124
|
+
if statement[CREATE_TABLE_AS_STMT]['into'] && statement[CREATE_TABLE_AS_STMT]['into'][INTO_CLAUSE]['rel']
|
125
|
+
from_clause_items << { item: statement[CREATE_TABLE_AS_STMT]['into'][INTO_CLAUSE]['rel'], type: :ddl }
|
126
|
+
end
|
127
|
+
if statement[CREATE_TABLE_AS_STMT]['query']
|
128
|
+
statements << statement[CREATE_TABLE_AS_STMT]['query']
|
129
|
+
end
|
130
|
+
when TRUNCATE_STMT
|
131
|
+
from_clause_items += statement.values[0]['relations'].map { |r| { item: r, type: :ddl } }
|
132
|
+
when VIEW_STMT
|
133
|
+
from_clause_items << { item: statement[VIEW_STMT]['view'], type: :ddl }
|
134
|
+
statements << statement[VIEW_STMT]['query']
|
135
|
+
when VACUUM_STMT, INDEX_STMT, CREATE_TRIG_STMT, RULE_STMT
|
136
|
+
from_clause_items << { item: statement.values[0]['relation'], type: :ddl }
|
137
|
+
when REFRESH_MAT_VIEW_STMT
|
138
|
+
from_clause_items << { item: statement[REFRESH_MAT_VIEW_STMT]['relation'], type: :ddl }
|
139
|
+
when DROP_STMT
|
140
|
+
objects = statement[DROP_STMT]['objects'].map do |obj|
|
141
|
+
if obj.is_a?(Array)
|
142
|
+
obj.map { |obj2| obj2['String'] && obj2['String']['str'] }
|
143
|
+
else
|
144
|
+
obj['String'] && obj['String']['str']
|
145
|
+
end
|
146
|
+
end
|
147
|
+
case statement[DROP_STMT]['removeType']
|
148
|
+
when OBJECT_TYPE_TABLE
|
149
|
+
@tables += objects.map { |r| { table: r.join('.'), type: :ddl } }
|
150
|
+
when OBJECT_TYPE_RULE, OBJECT_TYPE_TRIGGER
|
151
|
+
@tables += objects.map { |r| { table: r[0..-2].join('.'), type: :ddl } }
|
152
|
+
end
|
153
|
+
when GRANT_STMT
|
154
|
+
objects = statement[GRANT_STMT]['objects']
|
155
|
+
case statement[GRANT_STMT]['objtype']
|
156
|
+
when 0 # Column # rubocop:disable Lint/EmptyWhen
|
157
|
+
# FIXME
|
158
|
+
when 1 # Table
|
159
|
+
from_clause_items += objects.map { |o| { item: o, type: :ddl } }
|
160
|
+
when 2 # Sequence # rubocop:disable Lint/EmptyWhen
|
161
|
+
# FIXME
|
162
|
+
end
|
163
|
+
when LOCK_STMT
|
164
|
+
from_clause_items += statement.values[0]['relations'].map { |r| { item: r, type: :ddl } }
|
165
|
+
# The following are other statements that don't fit into query/DML/DDL
|
166
|
+
when EXPLAIN_STMT
|
167
|
+
statements << statement[EXPLAIN_STMT]['query']
|
168
|
+
end
|
169
|
+
|
170
|
+
statement_value = statement.values[0]
|
171
|
+
unless statement.empty?
|
172
|
+
subselect_items.concat(statement_value['targetList']) if statement_value['targetList']
|
173
|
+
subselect_items << statement_value['whereClause'] if statement_value['whereClause']
|
174
|
+
subselect_items.concat(statement_value['sortClause'].collect { |h| h[SORT_BY]['node'] }) if statement_value['sortClause']
|
175
|
+
subselect_items.concat(statement_value['groupClause']) if statement_value['groupClause']
|
176
|
+
subselect_items << statement_value['havingClause'] if statement_value['havingClause']
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
next_item = subselect_items.shift
|
181
|
+
if next_item
|
182
|
+
case next_item.keys[0]
|
183
|
+
when A_EXPR
|
184
|
+
%w[lexpr rexpr].each do |side|
|
185
|
+
elem = next_item.values[0][side]
|
186
|
+
next unless elem
|
187
|
+
if elem.is_a?(Array)
|
188
|
+
subselect_items += elem
|
189
|
+
else
|
190
|
+
subselect_items << elem
|
191
|
+
end
|
192
|
+
end
|
193
|
+
when BOOL_EXPR
|
194
|
+
subselect_items.concat(next_item.values[0]['args'])
|
195
|
+
when RES_TARGET
|
196
|
+
subselect_items << next_item[RES_TARGET]['val']
|
197
|
+
when SUB_LINK
|
198
|
+
statements << next_item[SUB_LINK]['subselect']
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
break if subselect_items.empty? && statements.empty?
|
203
|
+
end
|
204
|
+
|
205
|
+
loop do
|
206
|
+
next_item = from_clause_items.shift
|
207
|
+
break unless next_item && next_item[:item]
|
208
|
+
|
209
|
+
case next_item[:item].keys[0]
|
210
|
+
when JOIN_EXPR
|
211
|
+
%w[larg rarg].each do |side|
|
212
|
+
from_clause_items << { item: next_item[:item][JOIN_EXPR][side], type: next_item[:type] }
|
213
|
+
end
|
214
|
+
when ROW_EXPR
|
215
|
+
from_clause_items += next_item[:item][ROW_EXPR]['args'].map { |a| { item: a, type: next_item[:type] } }
|
216
|
+
when RANGE_VAR
|
217
|
+
rangevar = next_item[:item][RANGE_VAR]
|
218
|
+
next if !rangevar['schemaname'] && @cte_names.include?(rangevar['relname'])
|
219
|
+
|
220
|
+
table = [rangevar['schemaname'], rangevar['relname']].compact.join('.')
|
221
|
+
@tables << { table: table, type: next_item[:type] }
|
222
|
+
@aliases[rangevar['alias'][ALIAS]['aliasname']] = table if rangevar['alias']
|
223
|
+
when RANGE_SUBSELECT
|
224
|
+
from_clause_items << { item: next_item[:item][RANGE_SUBSELECT]['subquery'], type: next_item[:type] }
|
225
|
+
when SELECT_STMT
|
226
|
+
from_clause = next_item[:item][SELECT_STMT][FROM_CLAUSE_FIELD]
|
227
|
+
from_clause_items += from_clause.map { |r| { item: r, type: next_item[:type] } } if from_clause
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
@tables.uniq!
|
232
|
+
@cte_names.uniq!
|
233
|
+
end
|
234
|
+
|
235
|
+
def statements_and_cte_names_for_with_clause(with_clause)
|
236
|
+
statements = []
|
237
|
+
cte_names = []
|
238
|
+
|
239
|
+
with_clause[WITH_CLAUSE]['ctes'].each do |item|
|
240
|
+
next unless item[COMMON_TABLE_EXPR]
|
241
|
+
cte_names << item[COMMON_TABLE_EXPR]['ctename']
|
242
|
+
statements << item[COMMON_TABLE_EXPR]['ctequery']
|
243
|
+
end
|
244
|
+
|
245
|
+
[statements, cte_names]
|
246
|
+
end
|
247
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class PgQuery
|
2
|
+
private
|
3
|
+
|
4
|
+
def treewalker!(normalized_parsetree)
|
5
|
+
exprs = normalized_parsetree.dup.map { |e| [e, []] }
|
6
|
+
|
7
|
+
loop do
|
8
|
+
expr, parent_location = exprs.shift
|
9
|
+
|
10
|
+
if expr.is_a?(Hash)
|
11
|
+
expr.each do |k, v|
|
12
|
+
location = parent_location + [k]
|
13
|
+
|
14
|
+
yield(expr, k, v, location)
|
15
|
+
|
16
|
+
exprs << [v, location] unless v.nil?
|
17
|
+
end
|
18
|
+
elsif expr.is_a?(Array)
|
19
|
+
exprs += expr.map.with_index { |e, idx| [e, parent_location + [idx]] }
|
20
|
+
end
|
21
|
+
|
22
|
+
break if exprs.empty?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_tree_location(normalized_parsetree, searched_location)
|
27
|
+
treewalker! normalized_parsetree do |expr, k, v, location|
|
28
|
+
next unless location == searched_location
|
29
|
+
yield(expr, k, v)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def transform_nodes!(parsetree)
|
34
|
+
result = deep_dup(parsetree)
|
35
|
+
exprs = result.dup
|
36
|
+
|
37
|
+
loop do
|
38
|
+
expr = exprs.shift
|
39
|
+
|
40
|
+
if expr.is_a?(Hash)
|
41
|
+
yield(expr) if expr.size == 1 && expr.keys[0][/^[A-Z]+/]
|
42
|
+
|
43
|
+
exprs += expr.values.compact
|
44
|
+
elsif expr.is_a?(Array)
|
45
|
+
exprs += expr
|
46
|
+
end
|
47
|
+
|
48
|
+
break if exprs.empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
result
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class PgQuery
|
2
|
+
PossibleTruncation = Struct.new(:location, :node_type, :length, :is_array)
|
3
|
+
|
4
|
+
A_TRUNCATED = 'A_Truncated'.freeze
|
5
|
+
|
6
|
+
# Truncates the query string to be below the specified length, first trying to
|
7
|
+
# omit less important parts of the query, and only then cutting off the end.
|
8
|
+
def truncate(max_length)
|
9
|
+
output = deparse(@tree)
|
10
|
+
|
11
|
+
# Early exit if we're already below the max length
|
12
|
+
return output if output.size <= max_length
|
13
|
+
|
14
|
+
truncations = find_possible_truncations
|
15
|
+
|
16
|
+
# Truncate the deepest possible truncation that is the longest first
|
17
|
+
truncations.sort_by! { |t| [-t.location.size, -t.length] }
|
18
|
+
|
19
|
+
tree = deep_dup(@tree)
|
20
|
+
truncations.each do |truncation|
|
21
|
+
next if truncation.length < 3
|
22
|
+
|
23
|
+
find_tree_location(tree, truncation.location) do |expr, k|
|
24
|
+
expr[k] = { A_TRUNCATED => nil }
|
25
|
+
expr[k] = [expr[k]] if truncation.is_array
|
26
|
+
end
|
27
|
+
|
28
|
+
output = deparse(tree)
|
29
|
+
return output if output.size <= max_length
|
30
|
+
end
|
31
|
+
|
32
|
+
# We couldn't do a proper smart truncation, so we need a hard cut-off
|
33
|
+
output[0..max_length - 4] + '...'
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def find_possible_truncations
|
39
|
+
truncations = []
|
40
|
+
|
41
|
+
treewalker! @tree do |_expr, k, v, location|
|
42
|
+
case k
|
43
|
+
when TARGET_LIST_FIELD
|
44
|
+
length = deparse([{ SELECT_STMT => { k => v } }]).size - 7 # 'SELECT '.size
|
45
|
+
|
46
|
+
truncations << PossibleTruncation.new(location, TARGET_LIST_FIELD, length, true)
|
47
|
+
when 'whereClause'
|
48
|
+
length = deparse([{ SELECT_STMT => { k => v } }]).size
|
49
|
+
|
50
|
+
truncations << PossibleTruncation.new(location, 'whereClause', length, false)
|
51
|
+
when 'ctequery'
|
52
|
+
truncations << PossibleTruncation.new(location, 'ctequery', deparse([v]).size, false)
|
53
|
+
when 'cols'
|
54
|
+
truncations << PossibleTruncation.new(location, 'cols', deparse(v).size, true)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
truncations
|
59
|
+
end
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gitlab-pg_query
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lukas Fittl
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-10-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake-compiler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.49.1
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.49.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop-rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.15.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.15.1
|
69
|
+
description: Parses SQL queries using a copy of the PostgreSQL server query parser
|
70
|
+
email: lukas@fittl.com
|
71
|
+
executables: []
|
72
|
+
extensions:
|
73
|
+
- ext/pg_query/extconf.rb
|
74
|
+
extra_rdoc_files:
|
75
|
+
- CHANGELOG.md
|
76
|
+
- README.md
|
77
|
+
files:
|
78
|
+
- CHANGELOG.md
|
79
|
+
- LICENSE
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- ext/pg_query/extconf.rb
|
83
|
+
- ext/pg_query/pg_query_ruby.c
|
84
|
+
- ext/pg_query/pg_query_ruby.h
|
85
|
+
- ext/pg_query/pg_query_ruby.sym
|
86
|
+
- lib/pg_query.rb
|
87
|
+
- lib/pg_query/deep_dup.rb
|
88
|
+
- lib/pg_query/deparse.rb
|
89
|
+
- lib/pg_query/deparse/alter_table.rb
|
90
|
+
- lib/pg_query/deparse/interval.rb
|
91
|
+
- lib/pg_query/deparse/keywords.rb
|
92
|
+
- lib/pg_query/deparse/rename.rb
|
93
|
+
- lib/pg_query/filter_columns.rb
|
94
|
+
- lib/pg_query/fingerprint.rb
|
95
|
+
- lib/pg_query/legacy_parsetree.rb
|
96
|
+
- lib/pg_query/node_types.rb
|
97
|
+
- lib/pg_query/param_refs.rb
|
98
|
+
- lib/pg_query/parse.rb
|
99
|
+
- lib/pg_query/parse_error.rb
|
100
|
+
- lib/pg_query/treewalker.rb
|
101
|
+
- lib/pg_query/truncate.rb
|
102
|
+
- lib/pg_query/version.rb
|
103
|
+
homepage: http://github.com/lfittl/pg_query
|
104
|
+
licenses:
|
105
|
+
- BSD-3-Clause
|
106
|
+
metadata: {}
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options:
|
109
|
+
- "--main"
|
110
|
+
- README.md
|
111
|
+
- "--exclude"
|
112
|
+
- ext/
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
requirements: []
|
126
|
+
rubygems_version: 3.0.3
|
127
|
+
signing_key:
|
128
|
+
specification_version: 4
|
129
|
+
summary: PostgreSQL query parsing and normalization library
|
130
|
+
test_files: []
|