-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
sqlite: add tagged template #58748
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
sqlite: add tagged template #58748
Conversation
|
looking forward to your answers cc @nodejs/sqlite |
|
Since the API requires passing in the database instance, I'd prefer something like... const db = new DatabaseSync(":memory:");
const template = db.createSqlTag();As opposed to a standalone top-level function. |
|
Hello @0hmX . I wonder how this will fit the current API interface. I like @jasnell's suggestion. Do you think we could make it like the following? const db = new DatabaseSync(":memory:");
const template = db.createSqlTag();
template.run`...`;
template.get`...`
template.all`...`EDIT: I'm not sure if my question makes sense. I asked about it in the issue to get more information. |
|
From a technical point of view, @0hmX . This PR needs tests and documentation. The cache is important in the implementation since statements are meant to be reused. With template tags, we miss this. In such a situation, the cache is important, as in Matteo's implementation. I also wonder if we could have this implementation on C++ side, as all the implementations of |
|
@geeksilva97 and @jasnell, thank you for the feedback.
const { sql, DatabaseSync } = require("node:sql")
const db = new DatabaseSync(":memory:")
// adding sql template function in front for syntax highlight, else it's the same as using a multiline string
db.exec(sql`CREATE TABLE contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
phone TEXT NOT NULL UNIQUE,
email TEXT NOT NULL UNIQUE
)`)
// same as using a multiline string
db.exec(sql`INSERT INTO contacts (name, phone, email) VALUES (${"Alice"}, ${"111-222-3333"}, ${"alice@example.com"})`)
db.exec(sql`INSERT INTO contacts (name, phone, email) VALUES (${"Bob"}, ${"444-555-6666"}, ${"bob@example.com"})`)
db.exec(sql`INSERT INTO contacts (name, phone, email) VALUES (${"Charlie"}, ${"777-888-9999"}, ${"charlie@example.com"})`)
const cache = db.createCache()
const emailDomain = "%@example.com"
cache.all(sql`SELECT * FROM contacts WHERE email LIKE ${emailDomain}`)
const contactId = 2
cache.get(sql`SELECT * FROM contacts WHERE id = ${contactId}`)
const namePrefix = "C%"
cache.iterate(sql`SELECT * FROM contacts WHERE name LIKE ${namePrefix} ORDER BY name`) |
VSCode supports more complex patterns than just literally an identifier, I'm almost certain. It can be made to handle
It should very much not be the same as using a multiline string. The whole point of tagged templates - literally the only reason they are in the language - is that unlike strings they allow using a representation which is immune to problems like sql injection. From your implementation it looks like you're correctly avoiding sql injections, but this is something which should be emphasized in the docs and tested extensively. Users need to be aware that omitting the tag is not safe; it's not just a convenience for getting syntax highlighting. On that note, you should also be doing parsing of the string up front: users should get an error as soon as they write |
f5396ab to
b42e601
Compare
243ade6 to
e7d49d2
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #58748 +/- ##
==========================================
- Coverage 90.16% 90.02% -0.14%
==========================================
Files 636 646 +10
Lines 188060 189488 +1428
Branches 36899 37173 +274
==========================================
+ Hits 169568 170596 +1028
- Misses 11235 11533 +298
- Partials 7257 7359 +102
🚀 New features to boost your workflow:
|
a5260f2 to
d0b31a0
Compare
|
The PR is as stable as it gets now with all the tests passing. I need reviews on the PR. Look at the test file test-sqlite-template-tag.js to see how the api is designed. cc: @jasnell @geeksilva97 @bakkot also @mcollina (as creator of the main issues) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
07f36e0 to
579aa1f
Compare
Adding tagged template and LRU cache for prepared statements in SQLite.
Co-authored-by: Edy Silva <edigleyssonsilva@gmail.com>
Co-authored-by: Edy Silva <edigleyssonsilva@gmail.com>
Co-authored-by: Edy Silva <edigleyssonsilva@gmail.com>
Co-authored-by: Edy Silva <edigleyssonsilva@gmail.com>
|
|
||
| The store saves the prepared statement in respect to the SQL query provided, and | ||
| returns the same prepared statement when it sees it again. It has a `maxSize` | ||
| which defaults to 1000 but can also be passed in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's worth mentioning that all the methods do the automatic parameter binding
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what you mean. I figured 'prepared statements' as it implied we are doing automatic parameter, but I don't mind making it more explicit. I'll change it!
Co-authored-by: Edy Silva <edigleyssonsilva@gmail.com>
Co-authored-by: Edy Silva <edigleyssonsilva@gmail.com>
Co-authored-by: Edy Silva <edigleyssonsilva@gmail.com>
Co-authored-by: Edy Silva <edigleyssonsilva@gmail.com>
Co-authored-by: Edy Silva <edigleyssonsilva@gmail.com>
Co-authored-by: Edy Silva <edigleyssonsilva@gmail.com>
| sqlite3_step(stmt->statement_); | ||
| r = sqlite3_reset(stmt->statement_); | ||
| CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void()); | ||
| Local<Object> result = Object::New(env->isolate()); | ||
| sqlite3_int64 last_insert_rowid = | ||
| sqlite3_last_insert_rowid(stmt->db_->Connection()); | ||
| sqlite3_int64 changes = sqlite3_changes64(stmt->db_->Connection()); | ||
| Local<Value> last_insert_rowid_val; | ||
| Local<Value> changes_val; | ||
|
|
||
| if (stmt->use_big_ints_) { | ||
| last_insert_rowid_val = BigInt::New(env->isolate(), last_insert_rowid); | ||
| changes_val = BigInt::New(env->isolate(), changes); | ||
| } else { | ||
| last_insert_rowid_val = Number::New(env->isolate(), last_insert_rowid); | ||
| changes_val = Number::New(env->isolate(), changes); | ||
| } | ||
|
|
||
| if (result | ||
| ->Set(env->context(), | ||
| env->last_insert_rowid_string(), | ||
| last_insert_rowid_val) | ||
| .IsNothing() || | ||
| result->Set(env->context(), env->changes_string(), changes_val) | ||
| .IsNothing()) { | ||
| return; | ||
| } | ||
|
|
||
| info.GetReturnValue().Set(result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is quite similar to the StatementSync::Run. This seems like a duplication. Maybe we can come up with a method that can serve both StatementSync and SqlTagStore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This happens for the other methods too. I think we could handle duplications in all of them.
| @@ -490,6 +538,117 @@ times with different bound values. Parameters also offer protection against | |||
| [SQL injection][] attacks. For these reasons, prepared statements are preferred | |||
| over hand-crafted SQL strings when handling user input. | |||
|
|
|||
| ## Class: `SqlTagStore` | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more like a nit. What if we call it SQLTagStore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine by me
|
You've been doing a great job here, @0hmX . Thanks for that. I left a few more comments. |
|
Thanks, just wanted to ask, can we make this capital naming conversion some kind of lint rule? Do we have lint rules for C++? Then I will make an issue and a Pr for it! It will be much better for new C++ writers like me! |

Formed in 2009, the Archive Team (not to be confused with the archive.org Archive-It Team) is a rogue archivist collective dedicated to saving copies of rapidly dying or deleted websites for the sake of history and digital heritage. The group is 100% composed of volunteers and interested parties, and has expanded into a large amount of related projects for saving online and digital history.

Closes #57570
This pr introduces the support for tagged templates and an LRU to cache the templates. We introduced a new object called SqlTagStore that holds the ref to Lru. This acts as the main object that allows us to use tagged templates.
Look at the test file test-sqlite-template-tag.js to see how the api is designed.
[OLD]
The current implementation is experimental and intended to gather early feedback on the proposed direction.
This initial approach introduces two new functions:
processSqlTemplate(): [the actual string interpolation logic.]createSqlTag(): [a higher order function that stores the native db and returns another processSqlTemplate .]Note: The API design is not final and is expected to evolve based on feedback.
Questions for Maintainers
As this is my first feature development for the project, any feedback on the code, approach, or contribution process would be greatly appreciated.
Here is my current plane for the usage
[END OLD]