rasti-db 2.2.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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