rails-paradedb 0.7.0 → 0.9.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 +4 -4
- data/CHANGELOG.md +25 -1
- data/README.md +11 -5
- data/lib/parade_db/arel/builder.rb +19 -7
- data/lib/parade_db/arel/predications.rb +15 -7
- data/lib/parade_db/index.rb +1 -1
- data/lib/parade_db/migration_helpers.rb +4 -4
- data/lib/parade_db/search_methods.rb +22 -30
- data/lib/parade_db/tokenizer.rb +74 -72
- data/lib/parade_db/version.rb +1 -1
- metadata +3 -3
- data/lib/parade_db/arel/README.md +0 -76
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 25216744f6402f3ffdc06d8381f8ed801acec379dc15c71b92187b9e28a61b6d
|
|
4
|
+
data.tar.gz: 64dfcdcefa05ff52d0cd6b4b0292c005a0f4c2cf3a193ee07ac27bdae175ddd0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f3fb68ca97ffc5a30f6d503f15e97b02bc8db1de5aaeab020991848f777bc401fc62d06049490790ef75416b80c6a226edeb8cfe91e2bd8d8c3d76d12e95accb
|
|
7
|
+
data.tar.gz: 6c1e58314734c06d138a32f8a6928d13c7178e7db44e8460a45efbfb128e168d81f22dac53f74bc57ccd19f88f4ad0fdf60c3590dfcfd8d04e39b80d39faf531
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ All notable changes to this project will be documented in this file. The format
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
[0.9.0] - 2026-07-14
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Allow SQL/Arel function nodes as `match_all` and `match_any` query arguments.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **BREAKING**: Restrict match APIs to a single query argument.
|
|
16
|
+
- **BREAKING**: Rename relation query methods `matching_all` and `matching_any`
|
|
17
|
+
to `match_all` and `match_any`.
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
|
|
21
|
+
- **BREAKING**: Removed max_term_freq from the more_like_this API.
|
|
22
|
+
|
|
23
|
+
## [0.8.0] - 2026-06-15
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **BREAKING**: The `Tokenizer` class is now namespaced as `ParadeDB::Tokenizer`. Update references from `Tokenizer.simple(...)` to `ParadeDB::Tokenizer.simple(...)`. Schema dumps (`schema.rb`) now emit the fully-qualified constant.
|
|
28
|
+
|
|
7
29
|
## [0.7.0] - 2026-04-21
|
|
8
30
|
|
|
9
31
|
### Changed
|
|
@@ -132,7 +154,9 @@ All notable changes to this project will be documented in this file. The format
|
|
|
132
154
|
- Schema dump/load round-trip for tokenizer configuration and index options
|
|
133
155
|
(including `target_segment_count`)
|
|
134
156
|
|
|
135
|
-
[Unreleased]: https://github.com/paradedb/rails-paradedb/compare/v0.
|
|
157
|
+
[Unreleased]: https://github.com/paradedb/rails-paradedb/compare/v0.9.0...HEAD
|
|
158
|
+
[0.9.0]: https://github.com/paradedb/rails-paradedb/releases/tag/v0.9.0
|
|
159
|
+
[0.8.0]: https://github.com/paradedb/rails-paradedb/releases/tag/v0.8.0
|
|
136
160
|
[0.7.0]: https://github.com/paradedb/rails-paradedb/releases/tag/v0.7.0
|
|
137
161
|
[0.6.0]: https://github.com/paradedb/rails-paradedb/releases/tag/v0.6.0
|
|
138
162
|
[0.5.0]: https://github.com/paradedb/rails-paradedb/releases/tag/v0.5.0
|
data/README.md
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
<!-- ParadeDB: Postgres for Search and Analytics -->
|
|
2
1
|
<h1 align="center">
|
|
3
|
-
<a href="https://paradedb.com"
|
|
4
|
-
<
|
|
2
|
+
<a href="https://paradedb.com">
|
|
3
|
+
<picture align=center>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/paradedb/paradedb/raw/main/docs/logo/paradedb-logo-dark-large.svg">
|
|
5
|
+
<source media="(prefers-color-scheme: light)" srcset="https://github.com/paradedb/paradedb/raw/main/docs/logo/paradedb-logo-light-large.svg">
|
|
6
|
+
<img alt="The ParadeDB logo." src="https://github.com/paradedb/paradedb/raw/main/docs/logo/paradedb-logo-light-large.svg">
|
|
7
|
+
</picture>
|
|
8
|
+
</a>
|
|
9
|
+
<br>
|
|
5
10
|
</h1>
|
|
6
11
|
|
|
7
12
|
<p align="center">
|
|
8
|
-
<b>
|
|
13
|
+
<b>Search without a second system.</b><br/>
|
|
14
|
+
One Postgres for your application data, full-text search, vector retrieval, and aggregations.
|
|
9
15
|
</p>
|
|
10
16
|
|
|
11
17
|
<h3 align="center">
|
|
@@ -30,7 +36,7 @@
|
|
|
30
36
|
|
|
31
37
|
## ParadeDB for Rails
|
|
32
38
|
|
|
33
|
-
The official ActiveRecord integration for [ParadeDB](https://paradedb.com), including first-class support for managing BM25 indexes and running queries using the full ParadeDB API. Follow the [getting started guide](https://docs.paradedb.com/documentation/getting-started/environment#rails) to begin.
|
|
39
|
+
The official ActiveRecord integration for [ParadeDB](https://paradedb.com) (powered by the [`pg_search`](https://github.com/paradedb/paradedb) Postgres extension), including first-class support for managing BM25 indexes and running queries using the full ParadeDB API. Follow the [getting started guide](https://docs.paradedb.com/documentation/getting-started/environment#rails) to begin.
|
|
34
40
|
|
|
35
41
|
## Requirements & Compatibility
|
|
36
42
|
|
|
@@ -21,7 +21,7 @@ module ParadeDB
|
|
|
21
21
|
|
|
22
22
|
def match(
|
|
23
23
|
column,
|
|
24
|
-
|
|
24
|
+
term = nil,
|
|
25
25
|
tokenizer: nil,
|
|
26
26
|
distance: nil,
|
|
27
27
|
prefix: nil,
|
|
@@ -35,7 +35,7 @@ module ParadeDB
|
|
|
35
35
|
prefix: prefix,
|
|
36
36
|
transposition_cost_one: transposition_cost_one
|
|
37
37
|
)
|
|
38
|
-
rhs =
|
|
38
|
+
rhs = term_query_node(term)
|
|
39
39
|
rhs = apply_fuzzy(
|
|
40
40
|
rhs,
|
|
41
41
|
distance: distance,
|
|
@@ -50,7 +50,7 @@ module ParadeDB
|
|
|
50
50
|
|
|
51
51
|
def match_any(
|
|
52
52
|
column,
|
|
53
|
-
|
|
53
|
+
term = nil,
|
|
54
54
|
tokenizer: nil,
|
|
55
55
|
distance: nil,
|
|
56
56
|
prefix: nil,
|
|
@@ -64,7 +64,7 @@ module ParadeDB
|
|
|
64
64
|
prefix: prefix,
|
|
65
65
|
transposition_cost_one: transposition_cost_one
|
|
66
66
|
)
|
|
67
|
-
rhs =
|
|
67
|
+
rhs = term_query_node(term)
|
|
68
68
|
rhs = apply_fuzzy(
|
|
69
69
|
rhs,
|
|
70
70
|
distance: distance,
|
|
@@ -302,7 +302,7 @@ module ParadeDB
|
|
|
302
302
|
def apply_tokenizer(node, tokenizer)
|
|
303
303
|
return node if tokenizer.nil?
|
|
304
304
|
|
|
305
|
-
unless tokenizer.is_a?(Tokenizer)
|
|
305
|
+
unless tokenizer.is_a?(ParadeDB::Tokenizer)
|
|
306
306
|
raise ArgumentError, "tokenizer must be a Tokenizer"
|
|
307
307
|
end
|
|
308
308
|
|
|
@@ -538,12 +538,24 @@ module ParadeDB
|
|
|
538
538
|
)
|
|
539
539
|
end
|
|
540
540
|
|
|
541
|
-
def
|
|
542
|
-
joined =
|
|
541
|
+
def join_term(term)
|
|
542
|
+
joined = term.to_s
|
|
543
543
|
raise ArgumentError, "at least one search term is required" if joined.strip.empty?
|
|
544
544
|
joined
|
|
545
545
|
end
|
|
546
546
|
|
|
547
|
+
def term_query_node(term)
|
|
548
|
+
return term if arel_expression?(term)
|
|
549
|
+
|
|
550
|
+
quoted_value(join_term(term))
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def arel_expression?(value)
|
|
554
|
+
value.is_a?(::Arel::Nodes::Node) ||
|
|
555
|
+
value.is_a?(::Arel::Attributes::Attribute) ||
|
|
556
|
+
value.is_a?(::Arel::Nodes::SqlLiteral)
|
|
557
|
+
end
|
|
558
|
+
|
|
547
559
|
def normalize_term_set_terms(terms)
|
|
548
560
|
values = Array(terms).flatten.compact
|
|
549
561
|
raise ArgumentError, "term_set requires at least one value" if values.empty?
|
|
@@ -7,16 +7,16 @@ module ParadeDB
|
|
|
7
7
|
TOKENIZER_EXPRESSION = /\A[a-zA-Z_][a-zA-Z0-9_]*(?:(?:::|\.)[a-zA-Z_][a-zA-Z0-9_]*)*(?:\(\s*[a-zA-Z0-9_'".,=\s:-]*\s*\))?\z/.freeze
|
|
8
8
|
BUILDER = Builder.new.freeze
|
|
9
9
|
|
|
10
|
-
def pdb_match(
|
|
11
|
-
rhs =
|
|
10
|
+
def pdb_match(term = nil, tokenizer: nil, distance: nil, prefix: nil, transposition_cost_one: nil, boost: nil)
|
|
11
|
+
rhs = pdb_term_query_node(term)
|
|
12
12
|
rhs = pdb_apply_fuzzy(rhs, distance: distance, prefix: prefix, transposition_cost_one: transposition_cost_one)
|
|
13
13
|
rhs = pdb_apply_tokenizer(rhs, tokenizer)
|
|
14
14
|
rhs = pdb_apply_boost(rhs, boost)
|
|
15
15
|
::Arel::Nodes::InfixOperation.new("&&&", self, rhs)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def pdb_match_any(
|
|
19
|
-
rhs =
|
|
18
|
+
def pdb_match_any(term = nil, tokenizer: nil, distance: nil, prefix: nil, transposition_cost_one: nil, boost: nil)
|
|
19
|
+
rhs = pdb_term_query_node(term)
|
|
20
20
|
rhs = pdb_apply_fuzzy(rhs, distance: distance, prefix: prefix, transposition_cost_one: transposition_cost_one)
|
|
21
21
|
rhs = pdb_apply_tokenizer(rhs, tokenizer)
|
|
22
22
|
rhs = pdb_apply_boost(rhs, boost)
|
|
@@ -193,11 +193,19 @@ module ParadeDB
|
|
|
193
193
|
::Arel::Nodes.build_quoted(value)
|
|
194
194
|
end
|
|
195
195
|
|
|
196
|
-
def
|
|
197
|
-
|
|
196
|
+
def pdb_term_query_node(term)
|
|
197
|
+
return term if pdb_arel_expression?(term)
|
|
198
|
+
|
|
199
|
+
joined = term.to_s
|
|
198
200
|
raise ArgumentError, "at least one search term is required" if joined.strip.empty?
|
|
199
201
|
|
|
200
|
-
joined
|
|
202
|
+
pdb_quoted(joined)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def pdb_arel_expression?(value)
|
|
206
|
+
value.is_a?(::Arel::Nodes::Node) ||
|
|
207
|
+
value.is_a?(::Arel::Attributes::Attribute) ||
|
|
208
|
+
value.is_a?(::Arel::Nodes::SqlLiteral)
|
|
201
209
|
end
|
|
202
210
|
|
|
203
211
|
def pdb_validate_numeric!(value, name)
|
data/lib/parade_db/index.rb
CHANGED
|
@@ -53,7 +53,7 @@ module ParadeDB
|
|
|
53
53
|
|
|
54
54
|
class << self
|
|
55
55
|
def parse(source_name, tokenizer, context:)
|
|
56
|
-
unless tokenizer.is_a?(Tokenizer)
|
|
56
|
+
unless tokenizer.is_a?(ParadeDB::Tokenizer)
|
|
57
57
|
raise InvalidIndexDefinition, "#{context} for #{source_name.inspect} must be a Tokenizer"
|
|
58
58
|
end
|
|
59
59
|
|
|
@@ -422,7 +422,7 @@ module ParadeDB
|
|
|
422
422
|
|
|
423
423
|
with_sql = indexdef[start..pos - 2]
|
|
424
424
|
trailing_sql = indexdef[pos..]&.strip
|
|
425
|
-
trailing_sql = nil if trailing_sql
|
|
425
|
+
trailing_sql = nil if trailing_sql == ""
|
|
426
426
|
|
|
427
427
|
[with_sql, trailing_sql]
|
|
428
428
|
end
|
|
@@ -695,13 +695,13 @@ module ParadeDB
|
|
|
695
695
|
end
|
|
696
696
|
|
|
697
697
|
def bm25_tokenizer_ruby(name, positional_args, options)
|
|
698
|
-
if name.match?(/\A[a-z_][a-z0-9_]*\z/) && Tokenizer.respond_to?(name)
|
|
698
|
+
if name.match?(/\A[a-z_][a-z0-9_]*\z/) && ParadeDB::Tokenizer.respond_to?(name)
|
|
699
699
|
args = positional_args.map { |arg| ruby_literal(arg) }
|
|
700
700
|
args << "options: #{ruby_hash_literal(options)}" unless options.empty?
|
|
701
|
-
return "Tokenizer.#{name}(#{args.join(', ')})"
|
|
701
|
+
return "ParadeDB::Tokenizer.#{name}(#{args.join(', ')})"
|
|
702
702
|
end
|
|
703
703
|
|
|
704
|
-
"Tokenizer.new(#{name.inspect}, #{ruby_literal(positional_args.empty? ? nil : positional_args)}, #{ruby_literal(options.empty? ? nil : options)})"
|
|
704
|
+
"ParadeDB::Tokenizer.new(#{name.inspect}, #{ruby_literal(positional_args.empty? ? nil : positional_args)}, #{ruby_literal(options.empty? ? nil : options)})"
|
|
705
705
|
end
|
|
706
706
|
|
|
707
707
|
def split_sql_arguments(args_sql)
|
|
@@ -13,8 +13,6 @@ module ParadeDB
|
|
|
13
13
|
max_query_terms: :max_query_terms,
|
|
14
14
|
min_doc_freq: :min_doc_frequency,
|
|
15
15
|
min_doc_frequency: :min_doc_frequency,
|
|
16
|
-
max_term_freq: :max_term_frequency,
|
|
17
|
-
max_term_frequency: :max_term_frequency,
|
|
18
16
|
max_doc_freq: :max_doc_frequency,
|
|
19
17
|
max_doc_frequency: :max_doc_frequency,
|
|
20
18
|
min_word_length: :min_word_length,
|
|
@@ -25,7 +23,6 @@ module ParadeDB
|
|
|
25
23
|
min_term_frequency
|
|
26
24
|
max_query_terms
|
|
27
25
|
min_doc_frequency
|
|
28
|
-
max_term_frequency
|
|
29
26
|
max_doc_frequency
|
|
30
27
|
min_word_length
|
|
31
28
|
max_word_length
|
|
@@ -34,13 +31,11 @@ module ParadeDB
|
|
|
34
31
|
min_term_frequency
|
|
35
32
|
max_query_terms
|
|
36
33
|
min_doc_frequency
|
|
37
|
-
max_term_frequency
|
|
38
34
|
max_doc_frequency
|
|
39
35
|
min_word_length
|
|
40
36
|
max_word_length
|
|
41
37
|
stopwords
|
|
42
38
|
].freeze
|
|
43
|
-
|
|
44
39
|
# Internal state tracking
|
|
45
40
|
attr_accessor :_paradedb_current_field
|
|
46
41
|
attr_accessor :_paradedb_facet_fields
|
|
@@ -120,8 +115,8 @@ module ParadeDB
|
|
|
120
115
|
extending(SearchMethods).tap { |rel| rel._paradedb_current_field = search_column }
|
|
121
116
|
end
|
|
122
117
|
|
|
123
|
-
def
|
|
124
|
-
|
|
118
|
+
def match_all(
|
|
119
|
+
term = nil,
|
|
125
120
|
tokenizer: nil,
|
|
126
121
|
distance: nil,
|
|
127
122
|
prefix: nil,
|
|
@@ -131,21 +126,26 @@ module ParadeDB
|
|
|
131
126
|
)
|
|
132
127
|
require_search_field!
|
|
133
128
|
|
|
134
|
-
node =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
129
|
+
node =
|
|
130
|
+
if term.nil? && tokenizer.nil? && distance.nil? && prefix.nil? && transposition_cost_one.nil?
|
|
131
|
+
builder.match_all(_paradedb_current_field, boost: boost, constant_score: constant_score)
|
|
132
|
+
else
|
|
133
|
+
builder.match(
|
|
134
|
+
_paradedb_current_field,
|
|
135
|
+
term,
|
|
136
|
+
tokenizer: tokenizer,
|
|
137
|
+
distance: distance,
|
|
138
|
+
prefix: prefix,
|
|
139
|
+
transposition_cost_one: transposition_cost_one,
|
|
140
|
+
boost: boost,
|
|
141
|
+
constant_score: constant_score
|
|
142
|
+
)
|
|
143
|
+
end
|
|
144
144
|
where(grouped(node))
|
|
145
145
|
end
|
|
146
146
|
|
|
147
|
-
def
|
|
148
|
-
|
|
147
|
+
def match_any(
|
|
148
|
+
term,
|
|
149
149
|
tokenizer: nil,
|
|
150
150
|
distance: nil,
|
|
151
151
|
prefix: nil,
|
|
@@ -157,7 +157,7 @@ module ParadeDB
|
|
|
157
157
|
|
|
158
158
|
node = builder.match_any(
|
|
159
159
|
_paradedb_current_field,
|
|
160
|
-
|
|
160
|
+
term,
|
|
161
161
|
tokenizer: tokenizer,
|
|
162
162
|
distance: distance,
|
|
163
163
|
prefix: prefix,
|
|
@@ -168,10 +168,10 @@ module ParadeDB
|
|
|
168
168
|
where(grouped(node))
|
|
169
169
|
end
|
|
170
170
|
|
|
171
|
-
def excluding(
|
|
171
|
+
def excluding(term)
|
|
172
172
|
require_search_field!
|
|
173
173
|
|
|
174
|
-
neg = builder.match(_paradedb_current_field,
|
|
174
|
+
neg = builder.match(_paradedb_current_field, term)
|
|
175
175
|
where(grouped(neg.not))
|
|
176
176
|
end
|
|
177
177
|
|
|
@@ -273,14 +273,6 @@ module ParadeDB
|
|
|
273
273
|
where(grouped(node))
|
|
274
274
|
end
|
|
275
275
|
|
|
276
|
-
# Match-all wrapper for APIs that need an explicit ParadeDB predicate.
|
|
277
|
-
# Use with `.search(:id)` (or any indexed field): `Product.search(:id).match_all`.
|
|
278
|
-
def match_all(boost: nil, constant_score: nil)
|
|
279
|
-
require_search_field!
|
|
280
|
-
|
|
281
|
-
where(grouped(builder.match_all(_paradedb_current_field, boost: boost, constant_score: constant_score)))
|
|
282
|
-
end
|
|
283
|
-
|
|
284
276
|
# Exists wrapper to match rows where the indexed field has a value.
|
|
285
277
|
# Use with `.search(:id)` (or another exists-compatible indexed field).
|
|
286
278
|
def exists(boost: nil, constant_score: nil)
|
data/lib/parade_db/tokenizer.rb
CHANGED
|
@@ -1,95 +1,97 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
module ParadeDB
|
|
2
|
+
class Tokenizer
|
|
3
|
+
attr_reader :name, :positional_args, :options
|
|
4
|
+
|
|
5
|
+
def initialize(name, positional_args, options)
|
|
6
|
+
@name = name
|
|
7
|
+
@positional_args = positional_args
|
|
8
|
+
@options = options
|
|
9
|
+
end
|
|
3
10
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
end
|
|
11
|
+
def render()
|
|
12
|
+
if options.nil? && positional_args.nil?
|
|
13
|
+
return "pdb.#{name}"
|
|
14
|
+
end
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
args = []
|
|
17
|
+
if !positional_args.nil?
|
|
18
|
+
args.concat(positional_args.map { |x| render_positional_arg(x) })
|
|
19
|
+
end
|
|
20
|
+
if !options.nil?
|
|
21
|
+
args.concat(options.map {|k, v| quote_term("#{k}=#{v}")})
|
|
22
|
+
end
|
|
14
23
|
|
|
15
|
-
|
|
16
|
-
if !positional_args.nil?
|
|
17
|
-
args.concat(positional_args.map { |x| render_positional_arg(x) })
|
|
18
|
-
end
|
|
19
|
-
if !options.nil?
|
|
20
|
-
args.concat(options.map {|k, v| quote_term("#{k}=#{v}")})
|
|
24
|
+
return "pdb.#{name}(#{args.join(",")})"
|
|
21
25
|
end
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def self.whitespace(options: nil)
|
|
27
|
-
new("whitespace", nil, options)
|
|
28
|
-
end
|
|
27
|
+
def self.whitespace(options: nil)
|
|
28
|
+
new("whitespace", nil, options)
|
|
29
|
+
end
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
def self.unicode_words(options: nil)
|
|
32
|
+
new("unicode_words", nil, options)
|
|
33
|
+
end
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
def self.ngram(min_gram, max_gram, options: nil)
|
|
36
|
+
new("ngram", [min_gram, max_gram], options)
|
|
37
|
+
end
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
def self.simple(options: nil)
|
|
40
|
+
new("simple", nil, options)
|
|
41
|
+
end
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
def self.literal(options: nil)
|
|
44
|
+
new("literal", nil, options)
|
|
45
|
+
end
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
def self.literal_normalized(options: nil)
|
|
48
|
+
new("literal_normalized", nil, options)
|
|
49
|
+
end
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
def self.edge_ngram(min_gram, max_gram, options: nil)
|
|
52
|
+
new("edge_ngram", [min_gram, max_gram], options)
|
|
53
|
+
end
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
def self.regex_pattern(pattern, options: nil)
|
|
56
|
+
new("regex_pattern", [pattern], options)
|
|
57
|
+
end
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
def self.chinese_compatible(options: nil)
|
|
60
|
+
new("chinese_compatible", nil, options)
|
|
61
|
+
end
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
def self.lindera(dictionary, options: nil)
|
|
64
|
+
new("lindera", [dictionary], options)
|
|
65
|
+
end
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
def self.icu(options: nil)
|
|
68
|
+
new("icu", nil, options)
|
|
69
|
+
end
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
def self.jieba(options: nil)
|
|
72
|
+
new("jieba", nil, options)
|
|
73
|
+
end
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
def self.source_code(options: nil)
|
|
76
|
+
new("source_code", nil, options)
|
|
77
|
+
end
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
private
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
def quote_term(value)
|
|
82
|
+
escaped = value.gsub("'", "''")
|
|
83
|
+
"'#{escaped}'"
|
|
84
|
+
end
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
def render_positional_arg(value)
|
|
87
|
+
case value
|
|
88
|
+
when true, false, Numeric
|
|
89
|
+
value.to_s
|
|
90
|
+
when String
|
|
91
|
+
quote_term(value)
|
|
92
|
+
else
|
|
93
|
+
raise ArgumentError, "Unsupported tokenizer arg type: #{value.class}"
|
|
94
|
+
end
|
|
93
95
|
end
|
|
94
96
|
end
|
|
95
97
|
end
|
data/lib/parade_db/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails-paradedb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ParadeDB
|
|
@@ -83,7 +83,8 @@ dependencies:
|
|
|
83
83
|
- - "<"
|
|
84
84
|
- !ruby/object:Gem::Version
|
|
85
85
|
version: '9'
|
|
86
|
-
description:
|
|
86
|
+
description: Elastic-quality full-text search, vector retrieval, and aggregations
|
|
87
|
+
in Postgres via ParadeDB and ActiveRecord.
|
|
87
88
|
email:
|
|
88
89
|
- support@paradedb.com
|
|
89
90
|
executables: []
|
|
@@ -99,7 +100,6 @@ files:
|
|
|
99
100
|
- lib/parade_db.rb
|
|
100
101
|
- lib/parade_db/aggregations.rb
|
|
101
102
|
- lib/parade_db/arel.rb
|
|
102
|
-
- lib/parade_db/arel/README.md
|
|
103
103
|
- lib/parade_db/arel/builder.rb
|
|
104
104
|
- lib/parade_db/arel/nodes.rb
|
|
105
105
|
- lib/parade_db/arel/predications.rb
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# ParadeDB Arel Guide
|
|
2
|
-
|
|
3
|
-
This guide covers the low-level Arel API used by `rails-paradedb`.
|
|
4
|
-
Use it when you need explicit AST composition or direct SQL rendering.
|
|
5
|
-
|
|
6
|
-
## Quickstart
|
|
7
|
-
|
|
8
|
-
```ruby
|
|
9
|
-
require "parade_db/arel"
|
|
10
|
-
|
|
11
|
-
arel = ParadeDB::Arel::Builder.new(:products)
|
|
12
|
-
|
|
13
|
-
predicate = arel.match(:description, "running", "shoes")
|
|
14
|
-
.and(arel.regex(:description, "run.*"))
|
|
15
|
-
.and(arel.term(:in_stock, true))
|
|
16
|
-
|
|
17
|
-
sql = ParadeDB::Arel.to_sql(predicate, Product.connection)
|
|
18
|
-
# => ("products"."description" &&& 'running shoes' AND "products"."description" @@@ pdb.regex('run.*') AND "products"."in_stock" === TRUE)
|
|
19
|
-
|
|
20
|
-
Product.where(Arel.sql(sql))
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Render any node with `ParadeDB::Arel.to_sql(node)`. All nodes respond to
|
|
24
|
-
`.and`, `.or`, and `.not`.
|
|
25
|
-
|
|
26
|
-
## Builder Methods
|
|
27
|
-
|
|
28
|
-
| Method | ParadeDB SQL |
|
|
29
|
-
| ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
30
|
-
| `match(column, *terms, tokenizer: nil, distance:, prefix:, transposition_cost_one:, boost: nil, constant_score: nil)` | `column &&& 'a b'::pdb.whitespace::pdb.fuzzy(...)::pdb.boost(N)` |
|
|
31
|
-
| `match_any(column, *terms, tokenizer: nil, distance:, prefix:, transposition_cost_one:, boost: nil, constant_score: nil)` | `column \|\|\| 'a b'::pdb.whitespace::pdb.fuzzy(...)::pdb.boost(N)` |
|
|
32
|
-
| `phrase(column, text_or_terms, slop: n, tokenizer: nil, boost: nil, constant_score: nil)` | `column ### 'text'::pdb.slop(n)::pdb.whitespace` / `### ARRAY['a', 'b']::pdb.slop(n)` |
|
|
33
|
-
| `term(column, term, distance:, prefix:, transposition_cost_one:, boost: nil, constant_score: nil)` | `column === 'term'::pdb.fuzzy(...)::pdb.boost(N)` |
|
|
34
|
-
| `term_set(column, *terms, boost: nil, constant_score: nil)` | `column @@@ pdb.term_set(ARRAY[...])` |
|
|
35
|
-
| `regex(column, pattern, boost: nil, constant_score: nil)` | `column @@@ pdb.regex('pattern')` |
|
|
36
|
-
| `regex_phrase(column, *patterns, slop: nil, max_expansions: nil, boost: nil, constant_score: nil)` | `column @@@ pdb.regex_phrase(ARRAY['a', 'b'], slop => 2)` |
|
|
37
|
-
| `near(column, ParadeDB.proximity('a').within(d, 'b'))` | `column @@@ ('a' ## d ## 'b')` |
|
|
38
|
-
| `near(column, ParadeDB.proximity('a', 'b').within(d, ParadeDB.regex_term('c')).within(e, 'd'))` | `column @@@ ((pdb.prox_array('a', 'b') ## d ## pdb.prox_regex('c')) ## e ## 'd')` |
|
|
39
|
-
| `near(column, ParadeDB.proximity('a').within(d, 'b'), boost: 2.0)` | `column @@@ ('a' ## d ## 'b')::pdb.boost(2.0)` |
|
|
40
|
-
| `near(column, ParadeDB.proximity('a').within(d, 'b'), const: 1.0)` | `column @@@ ('a' ## d ## 'b')::pdb.const(1.0)` |
|
|
41
|
-
| `phrase_prefix(column, *terms, max_expansion: nil, boost: nil, constant_score: nil)` | `column @@@ pdb.phrase_prefix(ARRAY['a','b'][, 100])` |
|
|
42
|
-
| `parse(column, query, lenient: nil, conjunction_mode: nil, boost: nil, constant_score: nil)` | `column @@@ pdb.parse('q', lenient => true, conjunction_mode => true)` |
|
|
43
|
-
| `full_text(column, expr)` | `column @@@ expr` (raw right-hand value) |
|
|
44
|
-
| `match_all(column, boost: nil, constant_score: nil)` | `column @@@ pdb.all()` |
|
|
45
|
-
| `exists(column, boost: nil, constant_score: nil)` | `column @@@ pdb.exists()` |
|
|
46
|
-
| `range(column, value = nil, gte:, gt:, lte:, lt:, type:, boost: nil, constant_score: nil)` | `column @@@ pdb.range(int8range(3, 5, '[)'))` |
|
|
47
|
-
| `range_term(column, value, relation: nil, range_type: nil, boost: nil, constant_score: nil)` | `column @@@ pdb.range_term(1)` / `pdb.range_term('(1,2]'::int4range, 'Intersects')` |
|
|
48
|
-
| `more_like_this(column, key, fields: [:f1, :f2], options: {}, boost: nil, constant_score: nil)` | `column @@@ pdb.more_like_this(key, ARRAY['f1','f2'])` |
|
|
49
|
-
| `score(key_field)` | `pdb.score(key_field)` |
|
|
50
|
-
| `snippet(column, start, finish, max)` | `pdb.snippet(column, start, finish, max)` |
|
|
51
|
-
| `snippets(column, start_tag:, end_tag:, max_num_chars:, limit:, offset:, sort_by:)` | `pdb.snippets(column, ...)` |
|
|
52
|
-
| `snippet_positions(column)` | `pdb.snippet_positions(column)` |
|
|
53
|
-
| `agg(json, exact: nil)` | `pdb.agg(json[, false])` |
|
|
54
|
-
|
|
55
|
-
`Builder#[]` returns a column node for manual composition: `arel[:description]`.
|
|
56
|
-
|
|
57
|
-
> **Note:** `Builder` has no access to ActiveRecord model metadata.
|
|
58
|
-
> When calling `range_term` with a `relation:`, you must pass `range_type:` explicitly.
|
|
59
|
-
> The `SearchMethods` layer (`.search(:col).range_term(...)`) auto-infers `range_type` from the column's SQL type.
|
|
60
|
-
> Tokenizer overrides on `match`/`match_any` are mutually exclusive with fuzzy options.
|
|
61
|
-
|
|
62
|
-
## Composition
|
|
63
|
-
|
|
64
|
-
Boolean composition uses the standard helpers:
|
|
65
|
-
|
|
66
|
-
```ruby
|
|
67
|
-
fast = arel.match(:description, "running").and(arel.term(:rating, 4))
|
|
68
|
-
cheap = arel.match(:description, "budget")
|
|
69
|
-
predicate = fast.or(cheap.not)
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
`predicate` renders to:
|
|
73
|
-
|
|
74
|
-
```sql
|
|
75
|
-
(("products"."description" &&& 'running' AND "products"."rating" === 4) OR NOT ("products"."description" &&& 'budget'))
|
|
76
|
-
```
|