rasti-db 2.0.0 → 2.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +11 -3
- data/lib/rasti/db.rb +11 -1
- data/lib/rasti/db/collection.rb +10 -1
- data/lib/rasti/db/computed_attribute.rb +22 -0
- data/lib/rasti/db/nql/filter_condition_strategies/base.rb +17 -0
- data/lib/rasti/db/nql/filter_condition_strategies/postgres.rb +21 -0
- data/lib/rasti/db/nql/filter_condition_strategies/sqlite.rb +21 -0
- data/lib/rasti/db/nql/filter_condition_strategies/types/generic.rb +49 -0
- data/lib/rasti/db/nql/filter_condition_strategies/types/pg_array.rb +32 -0
- data/lib/rasti/db/nql/filter_condition_strategies/types/sqlite_array.rb +34 -0
- data/lib/rasti/db/nql/filter_condition_strategies/unsupported_type_comparison.rb +22 -0
- data/lib/rasti/db/nql/nodes/array_content.rb +21 -0
- data/lib/rasti/db/nql/nodes/attribute.rb +37 -0
- data/lib/rasti/db/nql/nodes/binary_node.rb +4 -0
- data/lib/rasti/db/nql/nodes/comparisons/base.rb +15 -1
- data/lib/rasti/db/nql/nodes/comparisons/equal.rb +0 -4
- data/lib/rasti/db/nql/nodes/comparisons/greater_than.rb +0 -4
- data/lib/rasti/db/nql/nodes/comparisons/greater_than_or_equal.rb +0 -4
- data/lib/rasti/db/nql/nodes/comparisons/include.rb +0 -4
- data/lib/rasti/db/nql/nodes/comparisons/less_than.rb +0 -4
- data/lib/rasti/db/nql/nodes/comparisons/less_than_or_equal.rb +0 -4
- data/lib/rasti/db/nql/nodes/comparisons/like.rb +0 -4
- data/lib/rasti/db/nql/nodes/comparisons/not_equal.rb +0 -4
- data/lib/rasti/db/nql/nodes/comparisons/not_include.rb +0 -4
- data/lib/rasti/db/nql/nodes/conjunction.rb +2 -2
- data/lib/rasti/db/nql/nodes/constants/array.rb +17 -0
- data/lib/rasti/db/nql/nodes/constants/base.rb +17 -0
- data/lib/rasti/db/nql/nodes/constants/false.rb +1 -1
- data/lib/rasti/db/nql/nodes/constants/float.rb +1 -1
- data/lib/rasti/db/nql/nodes/constants/integer.rb +1 -1
- data/lib/rasti/db/nql/nodes/constants/literal_string.rb +1 -1
- data/lib/rasti/db/nql/nodes/constants/string.rb +1 -1
- data/lib/rasti/db/nql/nodes/constants/time.rb +1 -1
- data/lib/rasti/db/nql/nodes/constants/true.rb +1 -1
- data/lib/rasti/db/nql/nodes/disjunction.rb +2 -2
- data/lib/rasti/db/nql/nodes/parenthesis_sentence.rb +6 -2
- data/lib/rasti/db/nql/nodes/sentence.rb +6 -2
- data/lib/rasti/db/nql/syntax.rb +262 -44
- data/lib/rasti/db/nql/syntax.treetop +27 -14
- data/lib/rasti/db/query.rb +42 -14
- data/lib/rasti/db/type_converters/postgres.rb +32 -36
- data/lib/rasti/db/type_converters/postgres_types/array.rb +11 -9
- data/lib/rasti/db/type_converters/postgres_types/hstore.rb +10 -9
- data/lib/rasti/db/type_converters/postgres_types/json.rb +17 -14
- data/lib/rasti/db/type_converters/postgres_types/jsonb.rb +17 -14
- data/lib/rasti/db/type_converters/sqlite.rb +62 -0
- data/lib/rasti/db/type_converters/sqlite_types/array.rb +34 -0
- data/lib/rasti/db/version.rb +1 -1
- data/rasti-db.gemspec +1 -0
- data/spec/collection_spec.rb +19 -11
- data/spec/computed_attribute_spec.rb +32 -0
- data/spec/minitest_helper.rb +32 -5
- data/spec/model_spec.rb +1 -1
- data/spec/nql/computed_attributes_spec.rb +29 -0
- data/spec/nql/filter_condition_spec.rb +23 -4
- data/spec/nql/filter_condition_strategies_spec.rb +112 -0
- data/spec/nql/syntax_parser_spec.rb +36 -5
- data/spec/query_spec.rb +254 -39
- data/spec/type_converters/sqlite_spec.rb +66 -0
- metadata +38 -3
- data/lib/rasti/db/nql/nodes/field.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cbeaaf85ac611e3a8a014720fca0ce2328c240e73b05cae04c4f9c9a3be6b7f6
|
4
|
+
data.tar.gz: 120f2ad079c6ea185bf95d4e74eec3a4d85bbb5bfb6941e8b8f923d5653c5e5f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a9479412975696063c5bf0d62e2f5a41d2f71afe98adb688356cd468a9dc6da04c9b02a5fcc434c8e3964d06bc1f96bfb6206fa6a8032e23587b1d68be9372b6
|
7
|
+
data.tar.gz: 5608471902a30b61971f03ad95e7b6d6d9fd62b8bf350d4bea55f840518e18f32201a9598f533882ee5a0c19d0634824d714fb3361f2f7fbdb976e4125819f46
|
data/README.md
CHANGED
@@ -93,7 +93,7 @@ User = Rasti::DB::Model[:id, :name, :posts, :comments, :person]
|
|
93
93
|
Post = Rasti::DB::Model[:id, :title, :body, :user_id, :user, :comments, :categories]
|
94
94
|
Comment = Rasti::DB::Model[:id, :text, :user_id, :user, :post_id, :post]
|
95
95
|
Category = Rasti::DB::Model[:id, :name, :posts]
|
96
|
-
Person = Rasti::DB::Model[:document_number, :first_name, :last_name, :birth_date, :user_id, :user]
|
96
|
+
Person = Rasti::DB::Model[:document_number, :first_name, :last_name, :full_name, :birth_date, :user_id, :user]
|
97
97
|
Language = Rasti::DB::Model[:id, :name, :people]
|
98
98
|
```
|
99
99
|
|
@@ -111,10 +111,10 @@ class Posts < Rasti::DB::Collection
|
|
111
111
|
many_to_many :categories
|
112
112
|
one_to_many :comments
|
113
113
|
|
114
|
-
query :created_by do |user_id|
|
114
|
+
query :created_by do |user_id|
|
115
115
|
where user_id: user_id
|
116
116
|
end
|
117
|
-
|
117
|
+
|
118
118
|
query :entitled, -> (title) { where title: title }
|
119
119
|
|
120
120
|
query :commented_by do |user_id|
|
@@ -144,6 +144,10 @@ class People < Rasti::DB::Collection
|
|
144
144
|
|
145
145
|
many_to_one :user
|
146
146
|
many_to_many :languages
|
147
|
+
|
148
|
+
computed_attribute :full_name do
|
149
|
+
Rasti::DB::ComputedAttribute.new Sequel.join([:first_name, ' ', :last_name])
|
150
|
+
end
|
147
151
|
end
|
148
152
|
|
149
153
|
class Languages < Rasti::DB::Collection
|
@@ -210,6 +214,10 @@ posts.all_attributes # => [Post, ...]
|
|
210
214
|
posts.graph('user.person').select_graph_attributes(user: [:id], 'user.person': [:last_name, :user_id]) # => [Post, ...]
|
211
215
|
posts.graph('user.person').exclude_graph_attributes(user: [:name], 'user.person': [:first_name, :last_name]) # => [Post, ...]
|
212
216
|
posts.graph('user.person').all_graph_attributes('user.person') # => [Post, ...]
|
217
|
+
|
218
|
+
posts.each { |post| do_something post } # Iterate posts loading all at first
|
219
|
+
posts.each(batch_size: 1000) { |post| do_something post } # Iterate posts loading in batches
|
220
|
+
posts.each_batch(size: 1000) { |posts| do_something posts } # Iterate batches of posts
|
213
221
|
```
|
214
222
|
### Natural Query Language
|
215
223
|
|
data/lib/rasti/db.rb
CHANGED
@@ -6,20 +6,25 @@ require 'treetop'
|
|
6
6
|
require 'hierarchical_graph'
|
7
7
|
require 'class_config'
|
8
8
|
require 'hash_ext'
|
9
|
+
require 'inflecto'
|
9
10
|
require 'multi_require'
|
10
11
|
|
11
12
|
module Rasti
|
12
13
|
module DB
|
13
|
-
|
14
|
+
|
14
15
|
extend MultiRequire
|
15
16
|
extend ClassConfig
|
16
17
|
|
17
18
|
require_relative 'db/query'
|
18
19
|
require_relative_pattern 'db/relations/*'
|
19
20
|
require_relative_pattern 'db/type_converters/postgres_types/*'
|
21
|
+
require_relative_pattern 'db/type_converters/sqlite_types/*'
|
22
|
+
require_relative 'db/nql/nodes/constants/base'
|
23
|
+
require_relative_pattern 'db/nql/filter_condition_strategies/types/*'
|
20
24
|
require_relative_pattern 'db/**/*'
|
21
25
|
|
22
26
|
attr_config :type_converters, []
|
27
|
+
attr_config :nql_filter_condition_strategy, nil
|
23
28
|
|
24
29
|
def self.to_db(db, collection_name, attribute_name, value)
|
25
30
|
type_converters.inject(value) do |result, type_converter|
|
@@ -33,5 +38,10 @@ module Rasti
|
|
33
38
|
end
|
34
39
|
end
|
35
40
|
|
41
|
+
def self.nql_filter_condition_for(comparison_name, identifier, argument)
|
42
|
+
raise 'Undefined Rasti::DB.nql_filter_condition_strategy' unless nql_filter_condition_strategy
|
43
|
+
nql_filter_condition_strategy.filter_condition_for comparison_name, identifier, argument
|
44
|
+
end
|
45
|
+
|
36
46
|
end
|
37
47
|
end
|
data/lib/rasti/db/collection.rb
CHANGED
@@ -14,7 +14,7 @@ module Rasti
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def collection_attributes
|
17
|
-
@collection_attributes ||= model.attributes - relations.keys
|
17
|
+
@collection_attributes ||= model.attributes - relations.keys - computed_attributes.keys
|
18
18
|
end
|
19
19
|
|
20
20
|
def primary_key
|
@@ -47,6 +47,10 @@ module Rasti
|
|
47
47
|
@queries ||= Hash::Indifferent.new
|
48
48
|
end
|
49
49
|
|
50
|
+
def computed_attributes
|
51
|
+
@computed_attributes ||= Hash::Indifferent.new
|
52
|
+
end
|
53
|
+
|
50
54
|
private
|
51
55
|
|
52
56
|
def set_collection_name(collection_name)
|
@@ -91,6 +95,11 @@ module Rasti
|
|
91
95
|
end
|
92
96
|
end
|
93
97
|
|
98
|
+
def computed_attribute(name, &block)
|
99
|
+
raise "Computed Attribute #{name} already exists" if computed_attributes.key? name
|
100
|
+
computed_attributes[name] = block.call
|
101
|
+
end
|
102
|
+
|
94
103
|
end
|
95
104
|
|
96
105
|
def initialize(environment)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
class ComputedAttribute
|
4
|
+
|
5
|
+
attr_reader :identifier
|
6
|
+
|
7
|
+
def initialize(identifier, &join)
|
8
|
+
@identifier = identifier
|
9
|
+
@join = join
|
10
|
+
end
|
11
|
+
|
12
|
+
def apply_join(dataset)
|
13
|
+
join ? join.call(dataset) : dataset
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :join
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module NQL
|
4
|
+
module FilterConditionStrategies
|
5
|
+
class Base
|
6
|
+
|
7
|
+
def filter_condition_for(comparison_name, identifier, argument)
|
8
|
+
type = type_for argument
|
9
|
+
raise UnsupportedTypeComparison.new(type, comparison_name) unless type.respond_to?(comparison_name)
|
10
|
+
type.public_send comparison_name, identifier, argument.value
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module NQL
|
4
|
+
module FilterConditionStrategies
|
5
|
+
class Postgres < Base
|
6
|
+
|
7
|
+
PG_TYPES = {
|
8
|
+
array: Types::PGArray
|
9
|
+
}
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def type_for(argument)
|
14
|
+
PG_TYPES.fetch(argument.type, Types::Generic)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module NQL
|
4
|
+
module FilterConditionStrategies
|
5
|
+
class SQLite < Base
|
6
|
+
|
7
|
+
SQLITE_TYPES = {
|
8
|
+
array: Types::SQLiteArray
|
9
|
+
}
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def type_for(argument)
|
14
|
+
SQLITE_TYPES.fetch(argument.type, Types::Generic)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module NQL
|
4
|
+
module FilterConditionStrategies
|
5
|
+
module Types
|
6
|
+
class Generic
|
7
|
+
|
8
|
+
def self.equal(identifier, value)
|
9
|
+
{identifier => value}
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.not_equal(identifier, value)
|
13
|
+
Sequel.negate equal(identifier, value)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.greater_than(identifier, value)
|
17
|
+
identifier > value
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.greater_than_or_equal(identifier, value)
|
21
|
+
identifier >= value
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.less_than(identifier, value)
|
25
|
+
identifier < value
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.less_than_or_equal(identifier, value)
|
29
|
+
identifier <= value
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.like(identifier, value)
|
33
|
+
Sequel.ilike identifier, value
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.include(identifier, value)
|
37
|
+
Sequel.ilike identifier, "%#{value}%"
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.not_include(identifier, value)
|
41
|
+
~include(identifier, value)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module NQL
|
4
|
+
module FilterConditionStrategies
|
5
|
+
module Types
|
6
|
+
class PGArray
|
7
|
+
|
8
|
+
def self.equal(identifier, values)
|
9
|
+
Sequel.&(
|
10
|
+
Sequel.pg_array(identifier).contains(Sequel.pg_array(values)),
|
11
|
+
Sequel.pg_array(identifier).contained_by(Sequel.pg_array(values))
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.not_equal(identifier, values)
|
16
|
+
~equal(identifier, values)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.include(identifier, values)
|
20
|
+
Sequel.pg_array(identifier).overlaps Sequel.pg_array(values)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.not_include(identifier, values)
|
24
|
+
~include(identifier, values)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module NQL
|
4
|
+
module FilterConditionStrategies
|
5
|
+
module Types
|
6
|
+
class SQLiteArray
|
7
|
+
|
8
|
+
def self.equal(identifier, values)
|
9
|
+
array = values.map { |value| "\"#{value}\"" }.join(',')
|
10
|
+
{identifier => "[#{array}]"}
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.not_equal(identifier, values)
|
14
|
+
Sequel.|(*values.map { |value| ~Sequel.like(identifier, "%\"#{value}\"%") })
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.like(identifier, values)
|
18
|
+
Sequel.|(*values.map { |value| Sequel.like(identifier, "%#{value}%") })
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.include(identifier, values)
|
22
|
+
Sequel.|(*values.map { |value| Sequel.like(identifier, "%\"#{value}\"%") })
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.not_include(identifier, values)
|
26
|
+
Sequel.&(*values.map { |value| ~Sequel.like(identifier, "%\"#{value}\"%") })
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module NQL
|
4
|
+
module FilterConditionStrategies
|
5
|
+
class UnsupportedTypeComparison < StandardError
|
6
|
+
|
7
|
+
attr_reader :argument_type, :comparison_name
|
8
|
+
|
9
|
+
def initialize(argument_type, comparison_name)
|
10
|
+
@argument_type = argument_type
|
11
|
+
@comparison_name = comparison_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def message
|
15
|
+
"Unsupported comparison #{comparison_name} for #{argument_type}"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module NQL
|
4
|
+
module Nodes
|
5
|
+
class ArrayContent < Treetop::Runtime::SyntaxNode
|
6
|
+
|
7
|
+
def values
|
8
|
+
[left.value] + right_value
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def right_value
|
14
|
+
right.is_a?(self.class) ? right.values : [right.value]
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Rasti
|
2
|
+
module DB
|
3
|
+
module NQL
|
4
|
+
module Nodes
|
5
|
+
class Attribute < Treetop::Runtime::SyntaxNode
|
6
|
+
|
7
|
+
def identifier(collection_class)
|
8
|
+
if computed? collection_class
|
9
|
+
collection_class.computed_attributes[column].identifier
|
10
|
+
else
|
11
|
+
tables.empty? ? Sequel[column] : Sequel[tables.join('__').to_sym][column]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def tables
|
16
|
+
_tables.elements.map{ |e| e.table.text_value }
|
17
|
+
end
|
18
|
+
|
19
|
+
def column
|
20
|
+
_column.text_value.to_sym
|
21
|
+
end
|
22
|
+
|
23
|
+
def computed_attributes(collection_class)
|
24
|
+
computed?(collection_class) ? [column] : []
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def computed?(collection_class)
|
30
|
+
collection_class.computed_attributes.key? column
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -8,6 +8,10 @@ module Rasti
|
|
8
8
|
values.flat_map(&:dependency_tables)
|
9
9
|
end
|
10
10
|
|
11
|
+
def computed_attributes(collection_class)
|
12
|
+
left.computed_attributes(collection_class) | right.computed_attributes(collection_class)
|
13
|
+
end
|
14
|
+
|
11
15
|
def values
|
12
16
|
@values ||= values_for(left) + values_for(right)
|
13
17
|
end
|
@@ -6,7 +6,21 @@ module Rasti
|
|
6
6
|
class Base < Treetop::Runtime::SyntaxNode
|
7
7
|
|
8
8
|
def dependency_tables
|
9
|
-
|
9
|
+
attribute.tables.empty? ? [] : [attribute.tables.join('.')]
|
10
|
+
end
|
11
|
+
|
12
|
+
def computed_attributes(collection_class)
|
13
|
+
attribute.computed_attributes(collection_class)
|
14
|
+
end
|
15
|
+
|
16
|
+
def filter_condition(collection_class)
|
17
|
+
DB.nql_filter_condition_for comparison_name, attribute.identifier(collection_class), argument
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def comparison_name
|
23
|
+
Inflecto.underscore(Inflecto.demodulize(self.class)).to_sym
|
10
24
|
end
|
11
25
|
|
12
26
|
end
|