jungle_path 0.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.
Files changed (129) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +22 -0
  6. data/README.md +5 -0
  7. data/jungle_path.gemspec +43 -0
  8. data/lib/jungle_path/api/helpers/auth.rb +45 -0
  9. data/lib/jungle_path/api/helpers/auth_local_user.rb +284 -0
  10. data/lib/jungle_path/api/helpers/auth_old.rb +232 -0
  11. data/lib/jungle_path/api/helpers/data_cache.rb +20 -0
  12. data/lib/jungle_path/api/helpers/defaults.rb +83 -0
  13. data/lib/jungle_path/api/helpers/logging.rb +36 -0
  14. data/lib/jungle_path/api/helpers/query_filters.rb +15 -0
  15. data/lib/jungle_path/api/helpers/rescues.rb +15 -0
  16. data/lib/jungle_path/api/helpers/result.rb +16 -0
  17. data/lib/jungle_path/api/helpers/standard_apis.rb +280 -0
  18. data/lib/jungle_path/api/helpers.rb +16 -0
  19. data/lib/jungle_path/api/template.erb +35 -0
  20. data/lib/jungle_path/api.rb +5 -0
  21. data/lib/jungle_path/app/a.gitignore +1 -0
  22. data/lib/jungle_path/app/api/server_base.rb +95 -0
  23. data/lib/jungle_path/app/api/server_custom.rb +121 -0
  24. data/lib/jungle_path/app/api/server_gen.rb +11 -0
  25. data/lib/jungle_path/app/auth/authorization.rb +96 -0
  26. data/lib/jungle_path/app/config/a.gitignore +1 -0
  27. data/lib/jungle_path/app/config/config.rb +240 -0
  28. data/lib/jungle_path/app/config/override.rb +3 -0
  29. data/lib/jungle_path/app/config.ru +28 -0
  30. data/lib/jungle_path/app/logs/log_files_go_here +0 -0
  31. data/lib/jungle_path/app/run.sh +4 -0
  32. data/lib/jungle_path/app/schemas/schema.rb +21 -0
  33. data/lib/jungle_path/app/schemas/schema_all_in_one.rb +181 -0
  34. data/lib/jungle_path/app.rb +8 -0
  35. data/lib/jungle_path/authentication/auth_provider/default.rb +83 -0
  36. data/lib/jungle_path/authentication/auth_provider.rb +7 -0
  37. data/lib/jungle_path/authentication/data_provider/default.rb +144 -0
  38. data/lib/jungle_path/authentication/data_provider.rb +7 -0
  39. data/lib/jungle_path/authentication/helpers.rb +19 -0
  40. data/lib/jungle_path/authentication/identity.rb +30 -0
  41. data/lib/jungle_path/authentication/password_hash.rb +124 -0
  42. data/lib/jungle_path/authentication.rb +9 -0
  43. data/lib/jungle_path/authorization/filter.rb +106 -0
  44. data/lib/jungle_path/authorization/paths.rb +71 -0
  45. data/lib/jungle_path/authorization.rb +5 -0
  46. data/lib/jungle_path/cache.rb +36 -0
  47. data/lib/jungle_path/config.rb +65 -0
  48. data/lib/jungle_path/controller/authentication.rb +129 -0
  49. data/lib/jungle_path/controller/base.rb +193 -0
  50. data/lib/jungle_path/controller/helpers.rb +47 -0
  51. data/lib/jungle_path/controller/template.erb +14 -0
  52. data/lib/jungle_path/controller.rb +7 -0
  53. data/lib/jungle_path/db_access/import/db_dir.rb +74 -0
  54. data/lib/jungle_path/db_access/import/delete.rb +30 -0
  55. data/lib/jungle_path/db_access/import/insert.rb +168 -0
  56. data/lib/jungle_path/db_access/import/schema.rb +34 -0
  57. data/lib/jungle_path/db_access/import/select.rb +68 -0
  58. data/lib/jungle_path/db_access/import.rb +15 -0
  59. data/lib/jungle_path/db_access/io/chunked_file_reader.rb +62 -0
  60. data/lib/jungle_path/db_access/io/config.rb +19 -0
  61. data/lib/jungle_path/db_access/io/copy.rb +73 -0
  62. data/lib/jungle_path/db_access/io/db.rb +82 -0
  63. data/lib/jungle_path/db_access/io/delete.rb +23 -0
  64. data/lib/jungle_path/db_access/io/init_db.rb +39 -0
  65. data/lib/jungle_path/db_access/io/insert.rb +24 -0
  66. data/lib/jungle_path/db_access/io/schema.rb +21 -0
  67. data/lib/jungle_path/db_access/io/select.rb +44 -0
  68. data/lib/jungle_path/db_access/io/update.rb +36 -0
  69. data/lib/jungle_path/db_access/io.rb +104 -0
  70. data/lib/jungle_path/db_model/column.rb +186 -0
  71. data/lib/jungle_path/db_model/params.rb +60 -0
  72. data/lib/jungle_path/db_model/schema.rb +100 -0
  73. data/lib/jungle_path/db_model/string.rb +9 -0
  74. data/lib/jungle_path/db_model/table.rb +307 -0
  75. data/lib/jungle_path/db_model.rb +34 -0
  76. data/lib/jungle_path/exceptions.rb +10 -0
  77. data/lib/jungle_path/gen/api.rb +52 -0
  78. data/lib/jungle_path/gen/controller.rb +0 -0
  79. data/lib/jungle_path/gen/db.rb +0 -0
  80. data/lib/jungle_path/gen/schema.rb +47 -0
  81. data/lib/jungle_path/gen/schema_tree/filter.rb +33 -0
  82. data/lib/jungle_path/gen/schema_tree/match_columns.rb +54 -0
  83. data/lib/jungle_path/gen/schema_tree/match_table_data.rb +22 -0
  84. data/lib/jungle_path/gen/schema_tree/match_tables.rb +70 -0
  85. data/lib/jungle_path/gen/schema_tree/node.rb +39 -0
  86. data/lib/jungle_path/gen/schema_tree.rb +105 -0
  87. data/lib/jungle_path/gen.rb +9 -0
  88. data/lib/jungle_path/json/base.rb +29 -0
  89. data/lib/jungle_path/json/time.rb +8 -0
  90. data/lib/jungle_path/json.rb +6 -0
  91. data/lib/jungle_path/logging.rb +23 -0
  92. data/lib/jungle_path/query/alias_info.rb +16 -0
  93. data/lib/jungle_path/query/engine.rb +878 -0
  94. data/lib/jungle_path/query/entity.rb +141 -0
  95. data/lib/jungle_path/query/field.rb +28 -0
  96. data/lib/jungle_path/query/field_primary_key.rb +27 -0
  97. data/lib/jungle_path/query/filter.rb +34 -0
  98. data/lib/jungle_path/query/float_value.rb +16 -0
  99. data/lib/jungle_path/query/from.rb +33 -0
  100. data/lib/jungle_path/query/int_value.rb +16 -0
  101. data/lib/jungle_path/query/limit.rb +19 -0
  102. data/lib/jungle_path/query/nested_hash_sorter.rb +94 -0
  103. data/lib/jungle_path/query/operator.rb +17 -0
  104. data/lib/jungle_path/query/query.rb +23 -0
  105. data/lib/jungle_path/query/sort_field.rb +34 -0
  106. data/lib/jungle_path/query/sql_string.rb +145 -0
  107. data/lib/jungle_path/query/string_value.rb +16 -0
  108. data/lib/jungle_path/query.rb +19 -0
  109. data/lib/jungle_path/rack/basic_credentials.rb +70 -0
  110. data/lib/jungle_path/rack/json_body_parser.rb +41 -0
  111. data/lib/jungle_path/rack.rb +6 -0
  112. data/lib/jungle_path/schema/auth.rb +83 -0
  113. data/lib/jungle_path/schema/base.rb +6 -0
  114. data/lib/jungle_path/schema/db.rb +10 -0
  115. data/lib/jungle_path/schema/version.rb +19 -0
  116. data/lib/jungle_path/schema.rb +8 -0
  117. data/lib/jungle_path/sql/auth_local_user.rb +5 -0
  118. data/lib/jungle_path/sql/general.rb +10 -0
  119. data/lib/jungle_path/sql/helpers.rb +11 -0
  120. data/lib/jungle_path/sql/key.rb +107 -0
  121. data/lib/jungle_path/sql/query_filter.rb +5 -0
  122. data/lib/jungle_path/sql/role.rb +5 -0
  123. data/lib/jungle_path/sql/user.rb +35 -0
  124. data/lib/jungle_path/sql/user_role.rb +5 -0
  125. data/lib/jungle_path/sql.rb +12 -0
  126. data/lib/jungle_path.rb +13 -0
  127. data/test.rb +33 -0
  128. data/test2.rb +15 -0
  129. metadata +200 -0
@@ -0,0 +1,878 @@
1
+ #require 'pry-byebug'
2
+ #require 'pp'
3
+ #use: binding.pry where you want a break point.
4
+ require 'set'
5
+ require 'jungle_path/query'
6
+ module JunglePath
7
+ module Query
8
+ class Engine
9
+ attr_reader :root, :tables, :user, :apply_limit_offset_to_sql
10
+
11
+ #def initialize(tables_hash, user=nil, apply_limit_offset_to_sql=true)
12
+ def initialize(node_tree, user=nil, apply_limit_offset_to_sql=true)
13
+ @tables = node_tree.tables_hash
14
+ @user = user
15
+ @apply_limit_offset_to_sql = apply_limit_offset_to_sql
16
+ #@root = Gen.gen_node_tree(tables_hash)
17
+ @root = node_tree
18
+ end
19
+
20
+ def self.run(query, db, level=7)
21
+ dataset = run_query(query, db)
22
+ rows = dataset_to_array(query, dataset) if level > 0
23
+ rows = sort_by_ids(query, rows) if level > 1
24
+ rows = transform_results_to_hash_array(query, rows) if level > 2
25
+ rows = add_arrays(query, rows) if level > 3
26
+ rows = combine_results(query, rows) if level > 4
27
+ rows = sort_by_original_sort(query, rows) if level > 5
28
+ rows = wrap_outer_objects(query, rows) if level > 6
29
+ if rows
30
+ if query.apply_limit_offset_to_sql
31
+ return rows
32
+ else
33
+ puts "query.root.limit: #{query.root.limit}."
34
+ puts "query.root.offset: #{query.root.offset}."
35
+ limit = 0
36
+ offset = 0
37
+ if query.root.limit and query.root.limit > 0
38
+ limit = query.root.limit
39
+ end
40
+ if query.root.offset and query.root.offset > 0
41
+ offset = query.root.offset
42
+ end
43
+ puts "rows.length: #{rows.length}."
44
+ return rows if limit == 0 and offset == 0
45
+ if offset > 0 and limit == 0
46
+ rows = rows[offset, -1]
47
+ rows = [] unless rows
48
+ return rows
49
+ end
50
+ rows = rows[offset, limit]
51
+ rows = [] unless rows
52
+ return rows
53
+ end
54
+ else
55
+ return dataset
56
+ end
57
+ end
58
+
59
+ def self.run_query(query, db)
60
+ ds = db[query.sql, *query.values]
61
+ end
62
+
63
+ def get_query_from_string(query_string)
64
+ tokens = tokenize(query_string).reverse
65
+ entity_root, values = parse_tokens(tokens)
66
+ #sql, aliases, symbols, sort_ids, primary_key_field_count = Engine.generate_sql @tables, (entity_root)
67
+ sql, aliases, symbols, sort_ids, primary_key_field_count = JunglePath::Query::SQLString.generate( self, (entity_root) )
68
+ JunglePath::Query::Query.new(entity_root, sql, values, aliases, symbols, sort_ids, primary_key_field_count, @apply_limit_offset_to_sql)
69
+ end
70
+
71
+ def parse_tokens(tokens)
72
+ aliases = ('a'..'z').to_a.reverse
73
+ doubles = aliases.map{|a| a + a} #also add in "aa", "bb", ... "zz"
74
+ aliases = doubles.push(*aliases)
75
+ values = []
76
+ entity_root = process_entity(nil, nil, tokens, aliases, values)
77
+ return entity_root, values
78
+ end
79
+
80
+ def next_token(tokens)
81
+ token = tokens.pop
82
+ token
83
+ end
84
+
85
+ def process_entity(parent_entity, token, tokens, aliases, values, symbol=nil, root_entity=nil, in_fields=false)
86
+ #puts "process_entity - parent: #{parent_entity != nil}; token: #{token}."
87
+ expect_entity = false
88
+ expect_fields = false
89
+ expect_filter = false
90
+ expect_sort = false
91
+ expect_limit = false
92
+ expect_offset = false
93
+ entity = nil
94
+ token = next_token(tokens) unless token
95
+ while token
96
+ if !entity
97
+ expect_entity = true
98
+ if token =~ /^[a-zA-Z_]/ # starts with alpha or underscore
99
+ #binding.pry
100
+ node = get_entity_node(token, parent_entity)
101
+ if node
102
+ entity = JunglePath::Query::Entity.new(token, aliases.pop, node, parent_entity, symbol)
103
+ root_entity = entity unless root_entity
104
+ expect_entity = false
105
+ expect_fields = true
106
+ else
107
+ raise "Entity #{token} was not found."
108
+ end
109
+ else
110
+ raise "Expected token to be an entity name. Token: #{token}"
111
+ end
112
+ elsif expect_offset and token.start_with? "["
113
+ expect_offset = false
114
+ entity.offset = JunglePath::Query::Limit.parse(token) #same format as Limit.
115
+ elsif expect_limit and token.start_with? "["
116
+ expect_limit = false
117
+ entity.limit = JunglePath::Query::Limit.parse(token)
118
+ expect_offset = true
119
+ elsif expect_sort and token == "("
120
+ if in_fields
121
+ raise "Sorts need to go in outer query -- they cannot be nested. Qualify fields using entity names. No qualification is needed for the outer entity."
122
+ end
123
+ expect_sort = false
124
+ entity.sort = process_sort(entity, tokens, aliases, root_entity)
125
+ expect_limit = true
126
+ elsif expect_filter and token == "("
127
+ expect_filter = false
128
+ entity.filter = process_filter(entity, tokens, aliases, values, root_entity)
129
+ if in_fields and tokens.length > 0 and (tokens.last == ")" or tokens.last == ",")
130
+ #puts "break"
131
+ break
132
+ end
133
+ expect_sort = true
134
+ elsif expect_fields and token == "("
135
+ expect_fields = false
136
+ entity.fields = process_fields(entity, tokens, aliases, values, root_entity)
137
+ #puts "back in entity"
138
+ if in_fields and tokens.length > 0 and (tokens.last == ")" or tokens.last == ",")
139
+ #puts "break"
140
+ break
141
+ end
142
+ expect_filter = true
143
+ else
144
+ raise "Unexpected token: #{token}."
145
+ end
146
+ token = next_token(tokens)
147
+ end
148
+ entity
149
+ end
150
+
151
+ def process_fields(entity, tokens, aliases, values, root_entity)
152
+ #puts "process_fields - entity.name: #{entity.name}."
153
+ fields = []
154
+ table = nil
155
+ token = next_token(tokens)
156
+ while token
157
+ #puts "token: #{token}."
158
+ if token == ")"
159
+ break
160
+ elsif token == ","
161
+ # do nothing
162
+ elsif token =~ /^[a-zA-Z_]/ # starts with alpha or underscore
163
+ node = get_entity_child_node(token, entity)
164
+
165
+ #Force PK fields onto field list:
166
+ if fields.length == 0
167
+ table_name = entity.node.child_table_name
168
+ table_name = entity.name unless table_name
169
+ #puts "#{entity.name}."
170
+ #puts "#{entity.node.child_table_name}."
171
+ table = tables[table_name]
172
+ table.primary_key_columns.values.each do |pk_column|
173
+ fields << JunglePath::Query::FieldPrimaryKey.new(pk_column.name, node, entity)
174
+ end
175
+ end
176
+
177
+ if node and node.child_table_name
178
+ # actually a table (entity).
179
+ field = process_entity(entity, token, tokens, aliases, values, node.symbol, root_entity, true)
180
+ field.field_node = node
181
+ fields << field
182
+ elsif node
183
+ #puts "token, node, entity: #{token}, #{node.name}, #{entity.name}."
184
+ field_symbol = token.downcase.to_sym
185
+ is_secure = table.secure_columns.include? field_symbol
186
+ fields << JunglePath::Query::Field.new(token, node, entity, is_secure) unless fields.map{|n| n.name}.include?(field_symbol)
187
+ else
188
+ raise "Token #{token} is not a valid table nor column name for entity: #{entity.name}.\nchild nodes: #{entity.node.nodes}."
189
+ end
190
+ else
191
+ raise "Token #{token} is not a valid table nor column name."
192
+ end
193
+ token = next_token(tokens)
194
+ end
195
+ fields
196
+ end
197
+
198
+ def process_filter(entity, tokens, aliases, values, root_entity)
199
+ #puts "process_filter..."
200
+ filter = []
201
+ nest_count = 0
202
+ expect_field = true
203
+ expect_value = false
204
+ expect_operator = false
205
+ expect_list = false
206
+ expect_connector = false
207
+ expect_comma = false
208
+ token = next_token(tokens)
209
+ #puts "token: #{token}"
210
+ while token
211
+ if expect_operator and JunglePath::Query::Engine.operators.include? token
212
+ expect_operator = false
213
+ filter << JunglePath::Query::Operator.parse(token)
214
+ if token == ">>" or token == "<<"
215
+ expect_list = true
216
+ else
217
+ expect_value = true
218
+ end
219
+ elsif expect_operator
220
+ raise "Expected token #{token} to be one of the following operators: #{Engine.operators.inspect}."
221
+ elsif expect_field and token == "("
222
+ nest_count += 1
223
+ filter << token
224
+ elsif expect_field and token == ")" and nest_count > 0
225
+ nest_count -= 1
226
+ filter << token
227
+ elsif expect_field and token == ")"
228
+ break
229
+ elsif expect_value and token == 'null'
230
+ # values << token #skip this token will use the literal null in filter.
231
+ filter << "null"
232
+ expect_value = false
233
+ expect_field = true
234
+ expect_connector = true
235
+ elsif expect_value and token =~ /^[0-9]/ # starts with number
236
+ if token.include? "."
237
+ values << JunglePath::Query::FloatValue.parse(token)
238
+ filter << "?"
239
+ else
240
+ values << JunglePath::Query::IntValue.parse(token)
241
+ filter << "?"
242
+ end
243
+ expect_value = false
244
+ expect_field = true
245
+ expect_connector = true
246
+ elsif expect_value and token =~ /^["']/ # starts with quote
247
+ values << JunglePath::Query::StringValue.parse(token)
248
+ filter << "?"
249
+ expect_value = false
250
+ expect_field = true
251
+ expect_connector = true
252
+ elsif expect_value and token.downcase == "true"
253
+ values << true
254
+ filter << "?"
255
+ expect_value = false
256
+ expect_field = true
257
+ expect_connector = true
258
+ elsif expect_value and token.downcase == "false"
259
+ values << false
260
+ filter << "?"
261
+ expect_value = false
262
+ expect_field = true
263
+ expect_connector = true
264
+ elsif expect_list and token == "("
265
+ filter << "("
266
+ filter << process_value_list(entity, tokens, aliases, root_entity, values)
267
+ expect_list = false
268
+ expect_field = true
269
+ elsif expect_field and expect_connector and JunglePath::Query::Engine.connectors.include? token.downcase
270
+ filter << token.downcase
271
+ expect_connector = false
272
+ elsif expect_field and Engine.connectors.include? token.downcase
273
+ raise "Did not expect connector token #{token}."
274
+ elsif expect_field and token =~ /^[a-zA-Z_]/ # starts with alpha or underscore
275
+ node = get_entity_child_node(token, entity)
276
+ if node and !node.child_table_name
277
+ #filter << Field.new(name, :column, node)
278
+ filter << "#{entity.alias_}.#{node.name}"
279
+ expect_field = false
280
+ expect_connector = false
281
+ expect_operator = true
282
+ else
283
+ raise "Token #{token} is not a valid column name."
284
+ end
285
+ else
286
+ raise "Token #{token} is not a valid column name. filter: #{filter.inspect}; tokens: #{tokens.inspect}."
287
+ end
288
+ token = next_token(tokens)
289
+ end
290
+ filter
291
+ end
292
+
293
+ def process_value_list(entity, tokens, aliases, root_entity, values)
294
+ list = []
295
+ expect_value = true
296
+ expect_comma = false
297
+ token = next_token(tokens)
298
+ while token
299
+ #puts "token: #{token}."
300
+ #puts "list: #{list.inspect}."
301
+ if list.empty? and token == ")"
302
+ list << ")"
303
+ break
304
+ elsif expect_comma and token == ")"
305
+ list << ")"
306
+ break
307
+ elsif expect_comma and token == ","
308
+ list << ", "
309
+ expect_comma = false
310
+ expect_value = true
311
+ elsif expect_comma
312
+ raise "Missing comma from 'in (list)'. list: #{list.inspect}; values: #{values.inspect}; tokens: #{tokens.inspect}."
313
+ elsif expect_value and token == ")"
314
+ raise "Found unexpected closing parenthisis: #{token} in value list following a comma: (,)."
315
+ elsif expect_value and token =~ /^[0-9]/ # starts with number
316
+ if token.include? "."
317
+ values << JunglePath::Query::FloatValue.filter(token)
318
+ list << "?"
319
+ else
320
+ values << JunglePath::Query::IntValue.parse(token)
321
+ list << "?"
322
+ end
323
+ expect_value = false
324
+ expect_comma = true
325
+ elsif expect_value and token =~ /^["']/ # starts with quote
326
+ values = JunglePath::Query::StringValue.parse(token)
327
+ list << "?"
328
+ expect_value = false
329
+ expect_comma = true
330
+ else
331
+ raise "Found unexpected token: #{token} in value list."
332
+ end
333
+ token = next_token(tokens)
334
+ end
335
+ list
336
+ end
337
+
338
+ def process_sort(entity, tokens, aliases, root_entity)
339
+ sort = []
340
+ sort_field = nil
341
+ expect_sort = false
342
+ token = next_token(tokens)
343
+ while token
344
+ if token == ")"
345
+ break
346
+ elsif token == ","
347
+ #do nothing
348
+ #sort << ", "
349
+ elsif expect_sort and JunglePath::Query::Engine.sorts.include? token.downcase
350
+ expect_sort = false
351
+ sort[-1].sort = token.downcase
352
+ elsif token =~ /^[a-zA-Z_]/ # starts with alpha or underscore
353
+ parts = token.downcase.split('.')
354
+ if parts.length > 1
355
+ table = parts[0].to_sym
356
+ field = parts[1].to_sym
357
+ else
358
+ table = nil
359
+ field = parts[0].to_sym
360
+ end
361
+ table_entity = nil
362
+ entities = nil
363
+ if table
364
+ table_entity, entities = find_table_entity(table, root_entity)
365
+ raise "Token #{token} leading part #{table} is not a valid table from your query." unless table_entity
366
+ end
367
+ if field
368
+ table_entity = entity unless table_entity
369
+ node = table_entity.node.nodes[field]
370
+ #puts "table_entity.name: #{table_entity.name}."
371
+ #puts "table_entity.node.name: #{table_entity.node.name}."
372
+ #puts "table_entity.node.nodes: #{table_entity.node.nodes}."
373
+ #puts "pp node:"
374
+ #pp node
375
+ #puts "zzz"
376
+ if node and node.child_table_name
377
+ raise "Field #{field} is not a valid field for table #{table} in your query,"
378
+ elsif node == nil and !@root.nodes[table_entity.name].nodes[field]
379
+ raise "Field #{field} is not a valid field for table #{table_entity.name} in your query,"
380
+ else
381
+ #sort_field = SortField.new(table, field, table_entity.node, node)
382
+ #sort << sort_field
383
+ #sort << "#{table_entity.alias_}.#{field}"
384
+ sort << JunglePath::Query::SortField.new("#{table_entity.alias_}.#{field}", field, entities)
385
+ expect_sort = true
386
+ end
387
+ end
388
+ else
389
+ raise "Token #{token} is not a valid table nor column name (or combination)."
390
+ end
391
+ token = next_token(tokens)
392
+ end
393
+ sort
394
+ end
395
+
396
+ def get_entity_node(token, parent_entity)
397
+ name = JunglePath::Query::Entity.get_name_from_token(token)
398
+ if parent_entity
399
+ node = parent_entity.node.nodes[name]
400
+ unless node
401
+ if parent_entity.node.child_table_name
402
+ node = @root.nodes[parent_entity.node.child_table_name].nodes[name]
403
+ else
404
+ node = @root.nodes[parent_entity.name].nodes[name]
405
+ end
406
+ end
407
+ end
408
+ node = @root.nodes[name] unless node
409
+
410
+ #new:
411
+ if node and node.class == ::Array
412
+ node = JunglePath::Query::Entity.select_node_from_array_by_token(node, token)
413
+ end
414
+
415
+ node
416
+ end
417
+
418
+ def get_entity_child_node(token, entity)
419
+ #puts "entity: #{entity.name}."
420
+ #puts "token: #{token}."
421
+ #puts "entity.node: #{entity.node}."
422
+ field_name = JunglePath::Query::Entity.get_name_from_token(token)
423
+ #puts "field_name: #{field_name}."
424
+ if entity.node.nodes.length > 0
425
+ node = entity.node.nodes[field_name]
426
+ else
427
+ #puts "entity.node.child_table_name: #{entity.node.child_table_name}."
428
+ node = @root.nodes[entity.node.child_table_name].nodes[field_name]
429
+ end
430
+
431
+ #new:
432
+ if node and node.class == ::Array #see gen_node_tree.rb line 60+.
433
+ # node is actually array of nodes, so pick correct one... token s/have column indicator, otherwise just grab first one.
434
+ node = JunglePath::Query::Entity.select_node_from_array_by_token(node, token)
435
+ end
436
+
437
+ node
438
+ end
439
+
440
+ def find_table_entity(name, parent_entity, level=0, entities=nil)
441
+ #binding.pry
442
+ entities = [] unless entities
443
+ entity = nil
444
+ parent_entity.fields.each do |field|
445
+ if field.is_entity? and field.name == name
446
+ entities << name
447
+ entity = field
448
+ break
449
+ elsif field.is_entity?
450
+ entities.push field.name
451
+ entity, entities = find_table_entity(name, field, level + 1, entities)
452
+ if entity
453
+ break
454
+ end
455
+ entities.pop
456
+ end
457
+ end
458
+ return entity, entities
459
+ end
460
+
461
+ def tokenize(query)
462
+ tokens = []
463
+ in_single_line_comment = nil
464
+ in_comment = 0
465
+ in_string = nil
466
+ in_escape = false
467
+ parens = 0
468
+ in_bracket = false
469
+ token = ""
470
+ i = 0
471
+ prev_char = nil
472
+ next_char = nil
473
+ query.each_char do |c|
474
+ i += 1
475
+ next_char = query[i]
476
+ if in_escape and c == "\\"
477
+ in_escape = false
478
+ token << "\\"
479
+ elsif in_escape and c == "t"
480
+ in_escape = false
481
+ token << "\t"
482
+ elsif in_escape and c == "n"
483
+ in_escape = false
484
+ token << "\n"
485
+ elsif in_escape and c == "r"
486
+ in_escape = false
487
+ token << "\r"
488
+ elsif in_escape and c == "\'"
489
+ in_escape = false
490
+ token << "\'"
491
+ elsif in_escape and c == "\""
492
+ in_escape = false
493
+ token << "\""
494
+ elsif in_escape
495
+ raise "Invalid character #{c} was escaped. You can only escape as follows: \\\\ \\t \\n \\r \\\' \\\" got it? :)"
496
+ elsif in_string and c.strip.empty? # is whitespace?
497
+ token << c
498
+ elsif in_string and in_string == c
499
+ token = token << c
500
+ tokens << token
501
+ token = ""
502
+ in_string = nil
503
+ elsif in_string and c == "\\"
504
+ in_escape = true
505
+ elsif in_string
506
+ token << c
507
+ elsif in_comment > 0 and c == "*"
508
+ #do nothing
509
+ elsif in_comment > 0 and prev_char == "*" and c == "/"
510
+ in_comment -= 1
511
+ elsif in_single_line_comment and (c == "\n" or c == "\r")
512
+ in_single_line_comment = false
513
+ elsif in_single_line_comment
514
+ #do nothing
515
+ elsif c == "/" and next_char == "*"
516
+ in_comment += 1
517
+ elsif in_comment > 0
518
+ #do nothing
519
+ elsif c == "-" and next_char == "-"
520
+ if token.length > 0
521
+ tokens << token
522
+ token = ""
523
+ end
524
+ in_single_line_comment = true
525
+ elsif in_bracket and c == "]"
526
+ in_bracket = false
527
+ token << c
528
+ tokens << token
529
+ token = ""
530
+ elsif in_bracket
531
+ token << c
532
+ elsif c == "\""
533
+ if token.length > 0
534
+ tokens << token
535
+ token = ""
536
+ end
537
+ in_string = c
538
+ token << c
539
+ elsif c == "'"
540
+ if token.length > 0
541
+ tokens << token
542
+ token = ""
543
+ end
544
+ in_string = c
545
+ token << c
546
+ elsif c.strip.empty? # is whitespace?
547
+ if token.length > 0
548
+ tokens << token
549
+ token = ""
550
+ end
551
+ elsif c == ","
552
+ if token.length > 0
553
+ tokens << token
554
+ token = ""
555
+ end
556
+ tokens << ","
557
+ elsif c == "("
558
+ parens += 1
559
+ if token.length > 0
560
+ tokens << token
561
+ token = ""
562
+ end
563
+ tokens << "("
564
+ elsif c == ")"
565
+ parens -= 1
566
+ if parens < 0
567
+ raise "Extra ')' character found at #{i}."
568
+ end
569
+ if token.length > 0
570
+ tokens << token
571
+ token = ""
572
+ end
573
+ tokens << ")"
574
+ elsif c == "["
575
+ in_bracket = true
576
+ if token.length > 0
577
+ tokens << token
578
+ token = ""
579
+ end
580
+ token << c
581
+ else
582
+ token << c
583
+ end
584
+ prev_char = c
585
+ end
586
+ if in_bracket
587
+ raise "No closing ']' character was found."
588
+ end
589
+ if parens > 0
590
+ raise "Missing #{parens} ')' characters."
591
+ end
592
+ if in_string
593
+ raise "Unclosed string: missing #{in_string} character."
594
+ end
595
+ if token.length > 0
596
+ tokens << token
597
+ token = ""
598
+ end
599
+ tokens
600
+ end
601
+
602
+ def self.operators
603
+ Set.new(["==", "!=", ">", "<", ">=", "<=", "~", "=~", "~~", ">>", "<<", "is", "is!"])
604
+ end
605
+
606
+ def self.operator_map
607
+ {"==" => "=", "!=" => "!=", ">" => ">", "<" => "<", ">=" => ">=", "<=" => "<=", "~" => "ilike", "=~" => "regex", "~~" => "full text search", ">>" => "in", "<<" => "not in", "is" => "is", "is!" => "is not"}
608
+ end
609
+
610
+ def self.connectors
611
+ Set.new(["and", "or"])
612
+ end
613
+
614
+ def self.sorts
615
+ Set.new(["asc", "desc"])
616
+ end
617
+
618
+ def self.dataset_to_array(query, dataset)
619
+ results = []
620
+ dataset.each do |row|
621
+ results << row
622
+ end
623
+ results
624
+ end
625
+
626
+ def self.sort_by_ids(query, results)
627
+ keys = query.sort_ids
628
+ if results.length > 0
629
+ results = results.sort_by {|h| (h.reject {|x| !keys.include?(x)}).values }
630
+ end
631
+ results
632
+ end
633
+
634
+ def self.calc_paths(query)
635
+ # For each sort field, find the path to its value in the nested hash.
636
+ # Returns an array containing an arrays of hash keys. (One array of keys for each sort field.)
637
+ sort_fields = query.root.sort
638
+ paths = []
639
+ sort_fields.each do |sort_field|
640
+ keys = []
641
+ paths << keys
642
+ if sort_field.entities
643
+ sort_field.entities.each do |e|
644
+ keys << e.to_sym
645
+ end
646
+ end
647
+ keys << sort_field.field_name
648
+ end
649
+ paths
650
+ end
651
+
652
+ def self.calc_sort_orders(query)
653
+ # For each sort field, add its ordering (:asc or :desc) into the returned array.
654
+ sort_fields = query.root.sort
655
+ sort_orders = []
656
+ sort_fields.each do |sort_field|
657
+ sort_orders << sort_field.sort.to_sym
658
+ end
659
+ sort_orders
660
+ end
661
+
662
+ def self.wrap_outer_objects(query, rows)
663
+ rows.map {|row| {query.root.name => row}}
664
+ end
665
+
666
+ def self.sort_by_original_sort(query, rows)
667
+ if query.root.sort and query.root.sort.length > 0
668
+ paths = calc_paths(query)
669
+ sort_orders = calc_sort_orders(query)
670
+ sorter = JunglePath::Query::NestedHashSorter.new(paths, sort_orders)
671
+ begin
672
+ #strange: when I added this debugging code, modes_of_action.uql sorting on code started working...
673
+ #was getting: ArgumentError - comparison of Hash with Hash failed.
674
+ #very strange!
675
+ count = 0
676
+ rows = rows.sort do |a, b|
677
+ count += 1
678
+ val = sorter.sort(a, b)
679
+ val
680
+ end
681
+ rescue
682
+ puts "count: #{count}."
683
+ raise
684
+ end
685
+ end
686
+ rows
687
+ end
688
+
689
+ def self.transform_results_to_hash_array(query, rows)
690
+ # transform results into nested hashes (for converting to json later...)
691
+ results = []
692
+ rows.each do |row|
693
+ #binding.pry
694
+ a_stack = []
695
+ h_stack = []
696
+ current_alias = nil
697
+ current_hash = nil
698
+ row.each do |k, v| #each item in this row...
699
+ new_alias, field_name = k.to_s.split(".").map{|a| a.to_sym}
700
+ parent_alias = query.aliases[new_alias].parent_alias
701
+ #parent_alias = parent_alias.to_sym if parent_alias
702
+
703
+ #load stack:
704
+ if current_alias
705
+ a_stack.push(current_alias)
706
+ h_stack.push(current_hash)
707
+ end
708
+
709
+ #unload stack:
710
+ index = a_stack.index(new_alias)
711
+ if index
712
+ # We've seen this new alias before. Get it (and the related hash) out of the stacks:
713
+ begin
714
+ current_alias = a_stack.pop
715
+ current_hash = h_stack.pop
716
+ end until current_alias == new_alias
717
+ current_hash[field_name] = v
718
+
719
+ elsif current_alias
720
+ if a_stack.include?(parent_alias)
721
+
722
+ #get parent hash
723
+ begin
724
+ temp_parent_alias = a_stack.pop
725
+ temp_parent_hash = h_stack.pop
726
+ end until temp_parent_alias == parent_alias
727
+
728
+ # put new hash into parent hash:
729
+ new_hash = {}
730
+ temp_parent_hash[query.aliases[new_alias.to_sym].name ] = new_hash # (convert alias to entity name)
731
+ a_stack.push temp_parent_alias
732
+ h_stack.push temp_parent_hash
733
+
734
+ # we now have new currents:
735
+ current_alias = new_alias
736
+ current_hash = new_hash
737
+ current_hash[field_name] = v
738
+ else
739
+ raise "parent_alias: #{parent_alias} was not found in a_stack: #{a_stack}. (h_stack: #{h_stack}).\nresults:\n#{results}."
740
+ end
741
+ else
742
+ #completely new:
743
+ current_alias = new_alias
744
+ current_hash = {}
745
+ current_hash[field_name] = v
746
+ end
747
+ end
748
+ # get the earliest parent (which contains all of the rest) and add to results:
749
+ while h_stack.length > 0
750
+ current_hash = h_stack.pop
751
+ end
752
+ results << current_hash
753
+ #break # <= for debugging only
754
+ end
755
+ results
756
+ end
757
+
758
+ def self.add_arrays(query, rows)
759
+ results = []
760
+ rows.each do |hash|
761
+ symbols = query.symbols.reverse
762
+ results << add_array(symbols, hash)
763
+ end
764
+ results
765
+ end
766
+
767
+ def self.add_array(symbols, hash)
768
+ result = {}
769
+ hash.each do |name, value|
770
+ if value.class == Hash
771
+ symbol = symbols.pop
772
+ if symbol == "<="
773
+ result[name] = []
774
+ result[name] << add_array(symbols, value)
775
+ else
776
+ result[name] = add_array(symbols, value)
777
+ end
778
+ else
779
+ result[name] = value
780
+ end
781
+ end
782
+ result
783
+ end
784
+
785
+ def self.primary_keys_match?(row_a, row_b, pk_fld_count)
786
+ # most common:
787
+ if pk_fld_count == 1
788
+ if row_a.first[1] == row_b.first[1]
789
+ return true
790
+ end
791
+ end
792
+
793
+ # multiple PKs:
794
+ values_a = row_a.values
795
+ values_b = row_b.values
796
+ values_a.each_index do |i|
797
+ if i == pk_fld_count
798
+ break
799
+ end
800
+ if values_a[i] != values_b[i]
801
+ return false
802
+ end
803
+ end
804
+ return true
805
+ end
806
+
807
+ def self.combine_results(query, rows)
808
+ results = []
809
+
810
+ aliases = {}
811
+ query.aliases.values.each do |ai|
812
+ aliases[ai.name] = ai
813
+ end
814
+
815
+ rows.each do |new_row|
816
+ if results.length == 0
817
+ results << new_row
818
+ else
819
+ begin
820
+ previous_row = results[-1]
821
+ if primary_keys_match?(previous_row, new_row, aliases.values[0].primary_key_columns_count)
822
+ #merge the row...
823
+ results[-1 ] = merge_hash(previous_row, new_row, aliases)
824
+ else
825
+ # add the row...
826
+ results << new_row
827
+ end
828
+ rescue
829
+ puts "rescue..."
830
+ puts previous_row
831
+ puts ""
832
+ puts new_row
833
+ puts ""
834
+ raise
835
+ end
836
+ end
837
+ end
838
+ results
839
+ end
840
+
841
+ def self.merge_hash(a_hash, b_hash, aliases) #pk_fld_counts)
842
+ h = {}
843
+ a = a_hash.to_a # [[name, value], [name, value], ... ]
844
+ b = b_hash.to_a # [[name, value], [name, value], ... ]
845
+ a.each_index do |i|
846
+ a_key = a[i][0] # name
847
+ a_value = a[i][1] # value
848
+ b_value = b[i][1] # value
849
+ if a_value.class == Array
850
+
851
+ pk_fld_count = aliases[a_key].primary_key_columns_count
852
+
853
+ h[a_key] = a_value # a array
854
+ a_hash = a_value[-1] # do we merge last element?
855
+ b_hash = b_value[0] # compare to value from new row...
856
+ if primary_keys_match?(a_hash, b_hash, pk_fld_count)
857
+ h[a_key][-1] = merge_hash(a_hash, b_hash, aliases)
858
+ else
859
+ h[a_key] << b_hash # add value from new row to array
860
+ end
861
+ elsif a_value.class == Hash
862
+ pk_fld_count = aliases[a_key].primary_key_columns_count
863
+
864
+ if primary_keys_match?(a_value, b_value, pk_fld_count)
865
+ h[a_key] = merge_hash(a_value, b_value, aliases)
866
+ else
867
+ raise "unexpected multiple values found!!! Expected\na_value: '#{a_value}'\npk to match\nb_value: '#{b_value}'\npk."
868
+ end
869
+ else
870
+ # regular field so a_value and b_value are the same.
871
+ h[a_key] = a_value
872
+ end
873
+ end
874
+ h
875
+ end
876
+ end
877
+ end
878
+ end