Skip to content

Commit a493c9f

Browse files
pimpinclaude
andcommitted
refactor(): interpolate type discriminator instead of binding it as a positional parameter
type/design_document is a class-level constant (equivalent to a table name) and should not consume a positional parameter slot. Interpolating it gives each model class its own query fingerprint for prepared-statement caching, consistent with how bucket.name is treated. Applies to all three callsites: n1ql.rb, relation.rb, and has_many.rb. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f6a1243 commit a493c9f

5 files changed

Lines changed: 22 additions & 28 deletions

File tree

lib/couchbase-orm/n1ql.rb

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,7 @@ def build_where(keys, values, params: nil)
100100
.reject { |key, value| key.nil? && value.nil? }
101101
.map { |key, value| build_match(key, value, params: params) }
102102
.join(" AND ")
103-
if params
104-
type_placeholder = bind(design_document, params)
105-
"type=#{type_placeholder} #{"AND " + where unless where.blank?}"
106-
else
107-
"type=\"#{design_document}\" #{"AND " + where unless where.blank?}"
108-
end
103+
"type=\"#{design_document}\" #{"AND " + where unless where.blank?}"
109104
end
110105

111106
# order-by-clause ::= ORDER BY ordering-term [ ',' ordering-term ]*

lib/couchbase-orm/relation.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def build_order
193193
end
194194

195195
def build_where_with_params(params)
196-
build_conds_with_params([[:type, @model.design_document]] + @where, params)
196+
build_conds_with_params([[nil, "type = #{@model.quote(@model.design_document)}"]] + @where, params)
197197
end
198198

199199
def build_conds_with_params(conds, params)

lib/couchbase-orm/utilities/has_many.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ def build_index_n1ql(klass, remote_class, remote_method, through_key, foreign_ke
9696
klass.class_eval do
9797
n1ql remote_method, emit_key: 'id', query_fn: proc { |bucket, values, options|
9898
raise ArgumentError, "values[0] must not be blank" if values[0].blank?
99-
n1ql_query = "SELECT raw #{through_key} FROM `#{bucket.name}` where type = $1 and #{foreign_key} = $2"
100-
params = [design_document, values[0]]
99+
n1ql_query = "SELECT raw #{through_key} FROM `#{bucket.name}` where type = \"#{design_document}\" and #{foreign_key} = $1"
100+
params = [values[0]]
101101
cluster.query(n1ql_query, Couchbase::Options::Query.new(
102102
positional_parameters: params,
103103
scan_consistency: options.instance_variable_get(:@scan_consistency)

spec/n1ql_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ class N1QLTest < CouchbaseOrm::Base
174174
N1QLTest.by_rating_reverse()
175175
expect(CouchbaseOrm.logger).to have_received(:debug).at_least(:once) do |&block|
176176
msg = block ? block.call : nil
177-
msg == "N1QL query: select raw meta().id from `#{CouchbaseOrm::Connection.bucket.name}` where type=$1 order by name DESC params: [\"n1_ql_test\"] return 0 rows with scan_consistency: #{described_class::DEFAULT_SCAN_CONSISTENCY}"
177+
msg == "N1QL query: select raw meta().id from `#{CouchbaseOrm::Connection.bucket.name}` where type=\"n1_ql_test\" order by name DESC params: [] return 0 rows with scan_consistency: #{described_class::DEFAULT_SCAN_CONSISTENCY}"
178178
end
179179
end
180180

@@ -185,14 +185,14 @@ class N1QLTest < CouchbaseOrm::Base
185185
N1QLTest.by_rating_reverse()
186186
expect(CouchbaseOrm.logger).to have_received(:debug).at_least(:once) do |&block|
187187
msg = block ? block.call : nil
188-
msg == "N1QL query: select raw meta().id from `#{CouchbaseOrm::Connection.bucket.name}` where type=$1 order by name DESC params: [\"n1_ql_test\"] return 0 rows with scan_consistency: not_bounded"
188+
msg == "N1QL query: select raw meta().id from `#{CouchbaseOrm::Connection.bucket.name}` where type=\"n1_ql_test\" order by name DESC params: [] return 0 rows with scan_consistency: not_bounded"
189189
end
190190

191191
CouchbaseOrm::N1ql.config(default_n1ql_config)
192192
N1QLTest.by_rating_reverse()
193193
expect(CouchbaseOrm.logger).to have_received(:debug).at_least(:once) do |&block|
194194
msg = block ? block.call : nil
195-
msg == "N1QL query: select raw meta().id from `#{CouchbaseOrm::Connection.bucket.name}` where type=$1 order by name DESC params: [\"n1_ql_test\"] return 0 rows with scan_consistency: #{described_class::DEFAULT_SCAN_CONSISTENCY}"
195+
msg == "N1QL query: select raw meta().id from `#{CouchbaseOrm::Connection.bucket.name}` where type=\"n1_ql_test\" order by name DESC params: [] return 0 rows with scan_consistency: #{described_class::DEFAULT_SCAN_CONSISTENCY}"
196196
end
197197
end
198198

spec/relation_spec.rb

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -314,49 +314,48 @@ def self.active
314314
it "should return parameterized query with to_n1ql_with_params" do
315315
relation = RelationModel.where(active: true, name: "Jane")
316316
n1ql, params = relation.send(:to_n1ql_with_params)
317-
expect(n1ql).to include("type = $1")
318-
expect(n1ql).to include("active = $2")
319-
expect(n1ql).to include("name = $3")
320-
expect(n1ql).not_to include("\"relation_model\"")
317+
expect(n1ql).to include("type = 'relation_model'")
318+
expect(n1ql).to include("active = $1")
319+
expect(n1ql).to include("name = $2")
321320
expect(n1ql).not_to include("'Jane'")
322-
expect(params).to eq(["relation_model", true, "Jane"])
321+
expect(params).to eq([true, "Jane"])
323322
end
324323

325324
it "should parameterize NOT conditions" do
326325
relation = RelationModel.not(active: true)
327326
n1ql, params = relation.send(:to_n1ql_with_params)
328-
expect(n1ql).to include("active != $2")
329-
expect(params).to eq(["relation_model", true])
327+
expect(n1ql).to include("active != $1")
328+
expect(params).to eq([true])
330329
end
331330

332331
it "should parameterize range conditions" do
333332
relation = RelationModel.where(age: 10..30)
334333
n1ql, params = relation.send(:to_n1ql_with_params)
335-
expect(n1ql).to include("age >= $2")
336-
expect(n1ql).to include("age <= $3")
337-
expect(params).to eq(["relation_model", 10, 30])
334+
expect(n1ql).to include("age >= $1")
335+
expect(n1ql).to include("age <= $2")
336+
expect(params).to eq([10, 30])
338337
end
339338

340339
it "should parameterize hash operator conditions" do
341340
relation = RelationModel.where(age: { _gte: 18, _lt: 65 })
342341
n1ql, params = relation.send(:to_n1ql_with_params)
343-
expect(n1ql).to include("age >= $2")
344-
expect(n1ql).to include("age < $3")
345-
expect(params).to eq(["relation_model", 18, 65])
342+
expect(n1ql).to include("age >= $1")
343+
expect(n1ql).to include("age < $2")
344+
expect(params).to eq([18, 65])
346345
end
347346

348347
it "should pass through string conditions without parameterization" do
349348
relation = RelationModel.where("active = true")
350349
n1ql, params = relation.send(:to_n1ql_with_params)
351350
expect(n1ql).to include("(active = true)")
352-
expect(params).to eq(["relation_model"])
351+
expect(params).to eq([])
353352
end
354353

355354
it "should parameterize array IN conditions" do
356355
relation = RelationModel.where(name: ["Alice", "Bob"])
357356
n1ql, params = relation.send(:to_n1ql_with_params)
358-
expect(n1ql).to include("name IN $2")
359-
expect(params).to eq(["relation_model", ["Alice", "Bob"]])
357+
expect(n1ql).to include("name IN $1")
358+
expect(params).to eq([["Alice", "Bob"]])
360359
end
361360
end
362361

0 commit comments

Comments
 (0)