rasti-db 2.2.0 → 3.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/lib/rasti/db.rb +16 -5
  4. data/lib/rasti/db/collection.rb +10 -10
  5. data/lib/rasti/db/data_source.rb +1 -1
  6. data/lib/rasti/db/model.rb +3 -104
  7. data/lib/rasti/db/nql/filter_condition_strategies/base.rb +17 -0
  8. data/lib/rasti/db/nql/filter_condition_strategies/postgres.rb +21 -0
  9. data/lib/rasti/db/nql/filter_condition_strategies/sqlite.rb +21 -0
  10. data/lib/rasti/db/nql/filter_condition_strategies/types/generic.rb +49 -0
  11. data/lib/rasti/db/nql/filter_condition_strategies/types/pg_array.rb +32 -0
  12. data/lib/rasti/db/nql/filter_condition_strategies/types/sqlite_array.rb +34 -0
  13. data/lib/rasti/db/nql/filter_condition_strategies/unsupported_type_comparison.rb +22 -0
  14. data/lib/rasti/db/nql/nodes/array_content.rb +21 -0
  15. data/lib/rasti/db/nql/nodes/binary_node.rb +1 -1
  16. data/lib/rasti/db/nql/nodes/comparisons/base.rb +10 -0
  17. data/lib/rasti/db/nql/nodes/comparisons/equal.rb +0 -4
  18. data/lib/rasti/db/nql/nodes/comparisons/greater_than.rb +0 -4
  19. data/lib/rasti/db/nql/nodes/comparisons/greater_than_or_equal.rb +0 -4
  20. data/lib/rasti/db/nql/nodes/comparisons/include.rb +0 -4
  21. data/lib/rasti/db/nql/nodes/comparisons/less_than.rb +0 -4
  22. data/lib/rasti/db/nql/nodes/comparisons/less_than_or_equal.rb +0 -4
  23. data/lib/rasti/db/nql/nodes/comparisons/like.rb +0 -4
  24. data/lib/rasti/db/nql/nodes/comparisons/not_equal.rb +0 -4
  25. data/lib/rasti/db/nql/nodes/comparisons/not_include.rb +0 -4
  26. data/lib/rasti/db/nql/nodes/constants/array.rb +17 -0
  27. data/lib/rasti/db/nql/nodes/constants/base.rb +17 -0
  28. data/lib/rasti/db/nql/nodes/constants/false.rb +1 -1
  29. data/lib/rasti/db/nql/nodes/constants/float.rb +1 -1
  30. data/lib/rasti/db/nql/nodes/constants/integer.rb +1 -1
  31. data/lib/rasti/db/nql/nodes/constants/literal_string.rb +1 -1
  32. data/lib/rasti/db/nql/nodes/constants/string.rb +1 -1
  33. data/lib/rasti/db/nql/nodes/constants/time.rb +1 -1
  34. data/lib/rasti/db/nql/nodes/constants/true.rb +1 -1
  35. data/lib/rasti/db/nql/nodes/parenthesis_sentence.rb +1 -1
  36. data/lib/rasti/db/nql/syntax.rb +229 -11
  37. data/lib/rasti/db/nql/syntax.treetop +24 -11
  38. data/lib/rasti/db/relations/base.rb +3 -3
  39. data/lib/rasti/db/relations/graph.rb +15 -15
  40. data/lib/rasti/db/relations/many_to_many.rb +4 -4
  41. data/lib/rasti/db/relations/many_to_one.rb +4 -4
  42. data/lib/rasti/db/relations/one_to_many.rb +2 -2
  43. data/lib/rasti/db/type_converters/postgres.rb +32 -36
  44. data/lib/rasti/db/type_converters/postgres_types/array.rb +11 -9
  45. data/lib/rasti/db/type_converters/postgres_types/hstore.rb +10 -9
  46. data/lib/rasti/db/type_converters/postgres_types/json.rb +17 -14
  47. data/lib/rasti/db/type_converters/postgres_types/jsonb.rb +17 -14
  48. data/lib/rasti/db/type_converters/sqlite.rb +62 -0
  49. data/lib/rasti/db/type_converters/sqlite_types/array.rb +34 -0
  50. data/lib/rasti/db/type_converters/time_in_zone.rb +1 -1
  51. data/lib/rasti/db/version.rb +1 -1
  52. data/rasti-db.gemspec +2 -0
  53. data/spec/collection_spec.rb +36 -28
  54. data/spec/minitest_helper.rb +4 -2
  55. data/spec/nql/filter_condition_spec.rb +19 -2
  56. data/spec/nql/filter_condition_strategies_spec.rb +112 -0
  57. data/spec/nql/syntax_parser_spec.rb +24 -0
  58. data/spec/query_spec.rb +89 -0
  59. data/spec/relations_spec.rb +17 -17
  60. data/spec/type_converters/sqlite_spec.rb +66 -0
  61. data/spec/type_converters/time_in_zone_spec.rb +1 -1
  62. metadata +46 -4
  63. data/spec/model_spec.rb +0 -90
@@ -47,39 +47,44 @@ module Rasti
47
47
  end
48
48
 
49
49
  rule comparison_include
50
- attribute:attribute space* comparator:':' space* argument:basic <Nodes::Comparisons::Include>
50
+ attribute:attribute space* comparator:':' space* argument:argument <Nodes::Comparisons::Include>
51
51
  end
52
52
 
53
53
  rule comparison_not_include
54
- attribute:attribute space* comparator:'!:' space* argument:basic <Nodes::Comparisons::NotInclude>
54
+ attribute:attribute space* comparator:'!:' space* argument:argument <Nodes::Comparisons::NotInclude>
55
55
  end
56
56
 
57
57
  rule comparison_like
58
- attribute:attribute space* comparator:'~' space* argument:basic <Nodes::Comparisons::Like>
58
+ attribute:attribute space* comparator:'~' space* argument:argument <Nodes::Comparisons::Like>
59
59
  end
60
60
 
61
61
  rule comparison_greater_than
62
- attribute:attribute space* comparator:'>' space* argument:basic <Nodes::Comparisons::GreaterThan>
62
+ attribute:attribute space* comparator:'>' space* argument:argument <Nodes::Comparisons::GreaterThan>
63
63
  end
64
64
 
65
65
  rule comparison_greater_than_or_equal
66
- attribute:attribute space* comparator:'>=' space* argument:basic <Nodes::Comparisons::GreaterThanOrEqual>
66
+ attribute:attribute space* comparator:'>=' space* argument:argument <Nodes::Comparisons::GreaterThanOrEqual>
67
67
  end
68
68
 
69
69
  rule comparison_less_than
70
- attribute:attribute space* comparator:'<' space* argument:basic <Nodes::Comparisons::LessThan>
70
+ attribute:attribute space* comparator:'<' space* argument:argument <Nodes::Comparisons::LessThan>
71
71
  end
72
72
 
73
73
  rule comparison_less_than_or_equal
74
- attribute:attribute space* comparator:'<=' space* argument:basic <Nodes::Comparisons::LessThanOrEqual>
74
+ attribute:attribute space* comparator:'<=' space* argument:argument <Nodes::Comparisons::LessThanOrEqual>
75
75
  end
76
76
 
77
77
  rule comparison_not_equal
78
- attribute:attribute space* comparator:'!=' space* argument:basic <Nodes::Comparisons::NotEqual>
78
+ attribute:attribute space* comparator:'!=' space* argument:argument <Nodes::Comparisons::NotEqual>
79
79
  end
80
80
 
81
81
  rule comparison_equal
82
- attribute:attribute space* comparator:'=' space* argument:basic <Nodes::Comparisons::Equal>
82
+ attribute:attribute space* comparator:'=' space* argument:argument <Nodes::Comparisons::Equal>
83
+ end
84
+
85
+ rule argument
86
+ array /
87
+ basic
83
88
  end
84
89
 
85
90
  rule basic
@@ -91,6 +96,14 @@ module Rasti
91
96
  string
92
97
  end
93
98
 
99
+ rule array
100
+ open:'[' space* contents:(array_content / basic) space* close:']' <Nodes::Constants::Array>
101
+ end
102
+
103
+ rule array_content
104
+ left:basic space* ',' space* right:(array_content / basic) <Nodes::ArrayContent>
105
+ end
106
+
94
107
  rule space
95
108
  [\s\t\n]
96
109
  end
@@ -130,7 +143,7 @@ module Rasti
130
143
  end
131
144
 
132
145
  rule valid_character
133
- [0-9a-zA-ZÁÀÄÂÃÅĀĂǍáàäâãåāăǎÉÈËÊĒĔĖĚéèëêēĕėěÍÌÏÎĨĬǏíìïîĩĭǐÓÒÖÔÕŌŎŐǑóòöôõōŏőǒÚÙÜÛŨŪŬŮŰǓúùüûũūŭůűǔÑñçÇ%@#+-_'?!$*/\s]
146
+ [0-9a-zA-ZÁÀÄÂÃÅĀĂǍáàäâãåāăǎÉÈËÊĒĔĖĚéèëêēĕėěÍÌÏÎĨĬǏíìïîĩĭǐÓÒÖÔÕŌŎŐǑóòöôõōŏőǒÚÙÜÛŨŪŬŮŰǓúùüûũūŭůűǔÑñçÇ%@#+\--Z\\^_'?!$*/\s]
134
147
  end
135
148
 
136
149
  rule boolean
@@ -159,7 +172,7 @@ module Rasti
159
172
  end
160
173
 
161
174
  rule reserved_character
162
- [&|.():!=<>~]
175
+ [&|.():!=<>~,\]\[]
163
176
  end
164
177
 
165
178
  end
@@ -2,7 +2,7 @@ module Rasti
2
2
  module DB
3
3
  module Relations
4
4
  class Base
5
-
5
+
6
6
  include Sequel::Inflections
7
7
 
8
8
  attr_reader :name, :source_collection_class
@@ -63,10 +63,10 @@ module Rasti
63
63
 
64
64
  def validate_join!
65
65
  if source_collection_class.data_source_name != target_collection_class.data_source_name
66
- raise "Invalid join of multiple data sources: #{source_collection_class.data_source_name}.#{source_collection_class.collection_name} > #{target_collection_class.data_source_name}.#{target_collection_class.collection_name}"
66
+ raise "Invalid join of multiple data sources: #{source_collection_class.data_source_name}.#{source_collection_class.collection_name} > #{target_collection_class.data_source_name}.#{target_collection_class.collection_name}"
67
67
  end
68
68
  end
69
-
69
+
70
70
  end
71
71
  end
72
72
  end
@@ -6,15 +6,15 @@ module Rasti
6
6
  def initialize(environment, collection_class, relations=[], selected_attributes={}, excluded_attributes={})
7
7
  @environment = environment
8
8
  @collection_class = collection_class
9
- @graph = build_graph relations,
10
- Hash::Indifferent.new(selected_attributes),
9
+ @graph = build_graph relations,
10
+ Hash::Indifferent.new(selected_attributes),
11
11
  Hash::Indifferent.new(excluded_attributes)
12
12
  end
13
13
 
14
14
  def merge(relations:[], selected_attributes:{}, excluded_attributes:{})
15
- Graph.new environment,
16
- collection_class,
17
- (flat_relations | relations),
15
+ Graph.new environment,
16
+ collection_class,
17
+ (flat_relations | relations),
18
18
  flat_selected_attributes.merge(selected_attributes),
19
19
  flat_excluded_attributes.merge(excluded_attributes)
20
20
  end
@@ -22,7 +22,7 @@ module Rasti
22
22
  def with_all_attributes_for(relations)
23
23
  relations_with_all_attributes = relations.map { |r| [r, nil] }.to_h
24
24
 
25
- merge selected_attributes: relations_with_all_attributes,
25
+ merge selected_attributes: relations_with_all_attributes,
26
26
  excluded_attributes: relations_with_all_attributes
27
27
  end
28
28
 
@@ -37,7 +37,7 @@ module Rasti
37
37
 
38
38
  graph.roots.each do |node|
39
39
  relation_of(node).fetch_graph environment,
40
- rows,
40
+ rows,
41
41
  node[:selected_attributes],
42
42
  node[:excluded_attributes] ,
43
43
  subgraph_of(node)
@@ -88,25 +88,25 @@ module Rasti
88
88
  excluded[id] = descendant[:excluded_attributes]
89
89
  end
90
90
 
91
- Graph.new environment,
92
- relation_of(node).target_collection_class,
93
- relations,
94
- selected,
91
+ Graph.new environment,
92
+ relation_of(node).target_collection_class,
93
+ relations,
94
+ selected,
95
95
  excluded
96
96
  end
97
97
 
98
98
  def build_graph(relations, selected_attributes, excluded_attributes)
99
99
  HierarchicalGraph.new.tap do |graph|
100
- flatten(relations).each do |relation|
100
+ flatten(relations).each do |relation|
101
101
  sections = relation.split('.')
102
-
102
+
103
103
  graph.add_node relation, name: sections.last.to_sym,
104
104
  selected_attributes: selected_attributes[relation],
105
105
  excluded_attributes: excluded_attributes[relation]
106
-
106
+
107
107
  if sections.count > 1
108
108
  parent_id = sections[0..-2].join('.')
109
- graph.add_relation parent_id: parent_id,
109
+ graph.add_relation parent_id: parent_id,
110
110
  child_id: relation
111
111
  end
112
112
  end
@@ -54,19 +54,19 @@ module Rasti
54
54
 
55
55
  relations_graph.fetch_graph join_rows if relations_graph
56
56
 
57
- relation_rows = join_rows.each_with_object(Hash.new { |h,k| h[k] = [] }) do |row, hash|
58
- attributes = row.select { |attr,_| target_collection_class.model.attributes.include? attr }
57
+ relation_rows = join_rows.each_with_object(Hash.new { |h,k| h[k] = [] }) do |row, hash|
58
+ attributes = row.select { |attr,_| target_collection_class.model.attribute_names.include? attr }
59
59
  hash[row[:source_foreign_key]] << target_collection_class.model.new(attributes)
60
60
  end
61
61
 
62
- rows.each do |row|
62
+ rows.each do |row|
63
63
  row[name] = relation_rows.fetch row[source_collection_class.primary_key], []
64
64
  end
65
65
  end
66
66
 
67
67
  def add_join(environment, dataset, prefix=nil)
68
68
  validate_join!
69
-
69
+
70
70
  many_to_many_relation_alias = with_prefix prefix, "#{relation_collection_name}_#{SecureRandom.base64}"
71
71
 
72
72
  relation_name = prefix ? Sequel[prefix] : Sequel[source_collection_class.collection_name]
@@ -17,18 +17,18 @@ module Rasti
17
17
  query = query.select_attributes(*selected_attributes) if selected_attributes
18
18
  query = relations_graph.apply_to query if relations_graph
19
19
 
20
- relation_rows = query.each_with_object({}) do |row, hash|
20
+ relation_rows = query.each_with_object({}) do |row, hash|
21
21
  hash[row.public_send(source_collection_class.primary_key)] = row
22
22
  end
23
-
24
- rows.each do |row|
23
+
24
+ rows.each do |row|
25
25
  row[name] = relation_rows[row[foreign_key]]
26
26
  end
27
27
  end
28
28
 
29
29
  def add_join(environment, dataset, prefix=nil)
30
30
  validate_join!
31
-
31
+
32
32
  relation_alias = join_relation_name prefix
33
33
 
34
34
  relation_name = prefix ? Sequel[prefix] : Sequel[source_collection_class.collection_name]
@@ -19,14 +19,14 @@ module Rasti
19
19
 
20
20
  relation_rows = query.group_by(&foreign_key)
21
21
 
22
- rows.each do |row|
22
+ rows.each do |row|
23
23
  row[name] = build_graph_result relation_rows.fetch(row[source_collection_class.primary_key], [])
24
24
  end
25
25
  end
26
26
 
27
27
  def add_join(environment, dataset, prefix=nil)
28
28
  validate_join!
29
-
29
+
30
30
  relation_alias = join_relation_name prefix
31
31
 
32
32
  relation_name = prefix ? Sequel[prefix] : Sequel[source_collection_class.collection_name]
@@ -3,62 +3,58 @@ module Rasti
3
3
  module TypeConverters
4
4
  class Postgres
5
5
 
6
- CONVERTERS = [PostgresTypes::JSON, PostgresTypes::JSONB, PostgresTypes::HStore, PostgresTypes::Array]
7
-
8
- @to_db_mapping = {}
6
+ CONVERTERS = [
7
+ PostgresTypes::JSON,
8
+ PostgresTypes::JSONB,
9
+ PostgresTypes::HStore,
10
+ PostgresTypes::Array
11
+ ]
9
12
 
10
13
  class << self
11
14
 
12
15
  def to_db(db, collection_name, attribute_name, value)
13
- to_db_mapping = to_db_mapping_for db, collection_name
14
-
15
- if to_db_mapping.key? attribute_name
16
- to_db_mapping[attribute_name][:converter].to_db value, to_db_mapping[attribute_name][:sub_type]
17
- else
18
- value
19
- end
16
+ converter, type = find_to_db_converter_and_type db, collection_name, attribute_name
17
+ converter ? converter.to_db(value, type) : value
20
18
  end
21
19
 
22
- def from_db(object)
23
- if from_db_mapping.key? object.class
24
- from_db_mapping[object.class].from_db object
25
- else
26
- object
27
- end
20
+ def from_db(value)
21
+ converter = find_from_db_converter value.class
22
+ converter ? converter.from_db(value) : value
28
23
  end
29
24
 
30
25
  private
31
26
 
32
- def to_db_mapping_for(db, collection_name)
33
- key = [db.opts[:database], collection_name]
34
-
35
- @to_db_mapping[key] ||= begin
36
- columns = Hash[db.schema(collection_name)]
27
+ def from_db_converters
28
+ @from_db_converters ||= {}
29
+ end
37
30
 
38
- columns.each_with_object({}) do |(name, schema), hash|
39
- CONVERTERS.each do |converter|
40
- unless hash.key? name
41
- match = converter.column_type_regex.match schema[:db_type]
31
+ def to_db_converters
32
+ @to_db_converters ||= {}
33
+ end
34
+
35
+ def find_to_db_converter_and_type(db, collection_name, attribute_name)
36
+ key = [db.opts[:database], collection_name]
42
37
 
43
- hash[name] = {converter: converter, sub_type: match.captures.first} if match
44
- end
45
- end
38
+ to_db_converters[key] ||= begin
39
+ columns = Hash[db.schema(collection_name)]
40
+ to_db_converters[key] = columns.each_with_object({}) do |(name, schema), hash|
41
+ converter = CONVERTERS.detect { |c| c.to_db? schema[:db_type] }
42
+ hash[name] = [converter, schema[:db_type]] if converter
46
43
  end
47
44
  end
45
+
46
+ to_db_converters[key].fetch(attribute_name, [])
48
47
  end
49
48
 
50
- def from_db_mapping
51
- @from_db_mapping ||= begin
52
- CONVERTERS.each_with_object({}) do |converter, result|
53
- converter.db_classes.each do |db_class|
54
- result[db_class] = converter
55
- end
56
- end
49
+ def find_from_db_converter(klass)
50
+ if !from_db_converters.key?(klass)
51
+ from_db_converters[klass] = CONVERTERS.detect { |c| c.from_db? klass }
57
52
  end
53
+
54
+ from_db_converters[klass]
58
55
  end
59
56
 
60
57
  end
61
-
62
58
  end
63
59
  end
64
60
  end
@@ -3,28 +3,30 @@ module Rasti
3
3
  module TypeConverters
4
4
  module PostgresTypes
5
5
  class Array
6
-
7
6
  class << self
8
7
 
9
- def column_type_regex
10
- /^([a-z]+)\[\]$/
8
+ DB_TYPE_REGEX = /^([a-z]+)\[\]$/
9
+
10
+ def to_db?(type)
11
+ !type.match(DB_TYPE_REGEX).nil?
11
12
  end
12
13
 
13
- def to_db(value, sub_type)
14
+ def to_db(value, type)
15
+ sub_type = type[0..-3]
14
16
  array = sub_type == 'hstore' ? value.map { |v| Sequel.hstore v } : value
15
17
  Sequel.pg_array array, sub_type
16
18
  end
17
19
 
18
- def db_classes
19
- [Sequel::Postgres::PGArray]
20
+ def from_db?(klass)
21
+ defined?(Sequel::Postgres::PGArray) &&
22
+ klass == Sequel::Postgres::PGArray
20
23
  end
21
24
 
22
- def from_db(object)
23
- object.to_a
25
+ def from_db(value)
26
+ value.to_a
24
27
  end
25
28
 
26
29
  end
27
-
28
30
  end
29
31
  end
30
32
  end
@@ -3,27 +3,28 @@ module Rasti
3
3
  module TypeConverters
4
4
  module PostgresTypes
5
5
  class HStore
6
-
7
6
  class << self
8
7
 
9
- def column_type_regex
10
- /^hstore$/
8
+ DB_TYPE_REGEX = /^hstore$/
9
+
10
+ def to_db?(type)
11
+ !type.match(DB_TYPE_REGEX).nil?
11
12
  end
12
13
 
13
- def to_db(value, sub_type)
14
+ def to_db(value, type)
14
15
  Sequel.hstore value
15
16
  end
16
17
 
17
- def db_classes
18
- [Sequel::Postgres::HStore]
18
+ def from_db?(klass)
19
+ defined?(Sequel::Postgres::HStore) &&
20
+ klass == Sequel::Postgres::HStore
19
21
  end
20
22
 
21
- def from_db(object)
22
- object.to_h
23
+ def from_db(value)
24
+ value.to_h
23
25
  end
24
26
 
25
27
  end
26
-
27
28
  end
28
29
  end
29
30
  end
@@ -3,36 +3,39 @@ module Rasti
3
3
  module TypeConverters
4
4
  module PostgresTypes
5
5
  class JSON
6
-
7
6
  class << self
8
7
 
9
- def column_type_regex
10
- /^json$/
8
+ DB_TYPE_REGEX = /^json$/
9
+
10
+ def to_db?(type)
11
+ !type.match(DB_TYPE_REGEX).nil?
11
12
  end
12
13
 
13
- def to_db(value, sub_type)
14
+ def to_db(value, type)
14
15
  Sequel.pg_json value
15
16
  end
16
17
 
17
- def db_classes
18
- @db_classes ||= from_db_convertions.keys
18
+ def from_db?(klass)
19
+ to_hash?(klass) || to_array?(klass)
19
20
  end
20
21
 
21
- def from_db(object)
22
- object.public_send from_db_convertions[object.class]
22
+ def from_db(value)
23
+ to_hash?(value.class) ? value.to_h : value.to_a
23
24
  end
24
25
 
25
26
  private
26
27
 
27
- def from_db_convertions
28
- @from_db_convertions ||= {
29
- Sequel::Postgres::JSONHash => :to_h,
30
- Sequel::Postgres::JSONArray => :to_a
31
- }
28
+ def to_hash?(klass)
29
+ defined?(Sequel::Postgres::JSONHash) &&
30
+ klass == Sequel::Postgres::JSONHash
32
31
  end
33
32
 
34
- end
33
+ def to_array?(klass)
34
+ defined?(Sequel::Postgres::JSONArray) &&
35
+ klass == Sequel::Postgres::JSONArray
36
+ end
35
37
 
38
+ end
36
39
  end
37
40
  end
38
41
  end