| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -230,10 +230,27 @@ added: v22.12.0 | |||
| 230 | 230 | * `options` {Object} The configuration options for how the changes will be applied. | |
| 231 | 231 | * `filter` {Function} Skip changes that, when targeted table name is supplied to this function, return a truthy value. | |
| 232 | 232 | By default, all changes are attempted. | |
| 233 | - * `onConflict` {number} Determines how conflicts are handled. **Default**: `SQLITE_CHANGESET_ABORT`. | ||
| 234 | - * `SQLITE_CHANGESET_OMIT`: conflicting changes are omitted. | ||
| 235 | - * `SQLITE_CHANGESET_REPLACE`: conflicting changes replace existing values. | ||
| 236 | - * `SQLITE_CHANGESET_ABORT`: abort on conflict and roll back database. | ||
| 233 | + * `onConflict` {Function} A function that determines how to handle conflicts. The function receives one argument, | ||
| 234 | + which can be one of the following values: | ||
| 235 | + | ||
| 236 | + * `SQLITE_CHANGESET_DATA`: A `DELETE` or `UPDATE` change does not contain the expected "before" values. | ||
| 237 | + * `SQLITE_CHANGESET_NOTFOUND`: A row matching the primary key of the `DELETE` or `UPDATE` change does not exist. | ||
| 238 | + * `SQLITE_CHANGESET_CONFLICT`: An `INSERT` change results in a duplicate primary key. | ||
| 239 | + * `SQLITE_CHANGESET_FOREIGN_KEY`: Applying a change would result in a foreign key violation. | ||
| 240 | + * `SQLITE_CHANGESET_CONSTRAINT`: Applying a change results in a `UNIQUE`, `CHECK`, or `NOT NULL` constraint | ||
| 241 | + violation. | ||
| 242 | + | ||
| 243 | + The function should return one of the following values: | ||
| 244 | + | ||
| 245 | + * `SQLITE_CHANGESET_OMIT`: Omit conflicting changes. | ||
| 246 | + * `SQLITE_CHANGESET_REPLACE`: Replace existing values with conflicting changes (only valid with | ||
| 247 | + `SQLITE_CHANGESET_DATA` or `SQLITE_CHANGESET_CONFLICT` conflicts). | ||
| 248 | + * `SQLITE_CHANGESET_ABORT`: Abort on conflict and roll back the database. | ||
| 249 | + | ||
| 250 | + When an error is thrown in the conflict handler or when any other value is returned from the handler, | ||
| 251 | + applying the changeset is aborted and the database is rolled back. | ||
| 252 | + | ||
| 253 | + **Default**: A function that returns `SQLITE_CHANGESET_ABORT`. | ||
| 237 | 254 | * Returns: {boolean} Whether the changeset was applied succesfully without being aborted. | |
| 238 | 255 | ||
| 239 | 256 | An exception is thrown if the database is not | |
@@ -486,9 +503,42 @@ An object containing commonly used constants for SQLite operations. | |||
| 486 | 503 | ||
| 487 | 504 | The following constants are exported by the `sqlite.constants` object. | |
| 488 | 505 | ||
| 489 | - #### Conflict-resolution constants | ||
| 506 | + #### Conflict resolution constants | ||
| 507 | + | ||
| 508 | + One of the following constants is available as an argument to the `onConflict` | ||
| 509 | + conflict resolution handler passed to [`database.applyChangeset()`][]. See also | ||
| 510 | + [Constants Passed To The Conflict Handler][] in the SQLite documentation. | ||
| 511 | + | ||
| 512 | + <table> | ||
| 513 | + <tr> | ||
| 514 | + <th>Constant</th> | ||
| 515 | + <th>Description</th> | ||
| 516 | + </tr> | ||
| 517 | + <tr> | ||
| 518 | + <td><code>SQLITE_CHANGESET_DATA</code></td> | ||
| 519 | + <td>The conflict handler is invoked with this constant when processing a DELETE or UPDATE change if a row with the required PRIMARY KEY fields is present in the database, but one or more other (non primary-key) fields modified by the update do not contain the expected "before" values.</td> | ||
| 520 | + </tr> | ||
| 521 | + <tr> | ||
| 522 | + <td><code>SQLITE_CHANGESET_NOTFOUND</code></td> | ||
| 523 | + <td>The conflict handler is invoked with this constant when processing a DELETE or UPDATE change if a row with the required PRIMARY KEY fields is not present in the database.</td> | ||
| 524 | + </tr> | ||
| 525 | + <tr> | ||
| 526 | + <td><code>SQLITE_CHANGESET_CONFLICT</code></td> | ||
| 527 | + <td>This constant is passed to the conflict handler while processing an INSERT change if the operation would result in duplicate primary key values.</td> | ||
| 528 | + </tr> | ||
| 529 | + <tr> | ||
| 530 | + <td><code>SQLITE_CHANGESET_CONSTRAINT</code></td> | ||
| 531 | + <td>If foreign key handling is enabled, and applying a changeset leaves the database in a state containing foreign key violations, the conflict handler is invoked with this constant exactly once before the changeset is committed. If the conflict handler returns <code>SQLITE_CHANGESET_OMIT</code>, the changes, including those that caused the foreign key constraint violation, are committed. Or, if it returns <code>SQLITE_CHANGESET_ABORT</code>, the changeset is rolled back.</td> | ||
| 532 | + </tr> | ||
| 533 | + <tr> | ||
| 534 | + <td><code>SQLITE_CHANGESET_FOREIGN_KEY</code></td> | ||
| 535 | + <td>If any other constraint violation occurs while applying a change (i.e. a UNIQUE, CHECK or NOT NULL constraint), the conflict handler is invoked with this constant.</td> | ||
| 536 | + </tr> | ||
| 537 | + </table> | ||
| 490 | 538 | ||
| 491 | - The following constants are meant for use with [`database.applyChangeset()`](#databaseapplychangesetchangeset-options). | ||
| 539 | + One of the following constants must be returned from the `onConflict` conflict | ||
| 540 | + resolution handler passed to [`database.applyChangeset()`][]. See also | ||
| 541 | + [Constants Returned From The Conflict Handler][] in the SQLite documentation. | ||
| 492 | 542 | ||
| 493 | 543 | <table> | |
| 494 | 544 | <tr> | |
@@ -501,7 +551,7 @@ The following constants are meant for use with [`database.applyChangeset()`](#da | |||
| 501 | 551 | </tr> | |
| 502 | 552 | <tr> | |
| 503 | 553 | <td><code>SQLITE_CHANGESET_REPLACE</code></td> | |
| 504 | - <td>Conflicting changes replace existing values.</td> | ||
| 554 | + <td>Conflicting changes replace existing values. Note that this value can only be returned when the type of conflict is either <code>SQLITE_CHANGESET_DATA</code> or <code>SQLITE_CHANGESET_CONFLICT</code>.</td> | ||
| 505 | 555 | </tr> | |
| 506 | 556 | <tr> | |
| 507 | 557 | <td><code>SQLITE_CHANGESET_ABORT</code></td> | |
@@ -510,11 +560,14 @@ The following constants are meant for use with [`database.applyChangeset()`](#da | |||
| 510 | 560 | </table> | |
| 511 | 561 | ||
| 512 | 562 | [Changesets and Patchsets]: https://www.sqlite.org/sessionintro.html#changesets_and_patchsets | |
| 563 | + [Constants Passed To The Conflict Handler]: https://www.sqlite.org/session/c_changeset_conflict.html | ||
| 564 | + [Constants Returned From The Conflict Handler]: https://www.sqlite.org/session/c_changeset_abort.html | ||
| 513 | 565 | [SQL injection]: https://en.wikipedia.org/wiki/SQL_injection | |
| 514 | 566 | [`ATTACH DATABASE`]: https://www.sqlite.org/lang_attach.html | |
| 515 | 567 | [`PRAGMA foreign_keys`]: https://www.sqlite.org/pragma.html#pragma_foreign_keys | |
| 516 | 568 | [`SQLITE_DETERMINISTIC`]: https://www.sqlite.org/c3ref/c_deterministic.html | |
| 517 | 569 | [`SQLITE_DIRECTONLY`]: https://www.sqlite.org/c3ref/c_deterministic.html | |
| 570 | + [`database.applyChangeset()`]: #databaseapplychangesetchangeset-options | ||
| 518 | 571 | [`sqlite3_changes64()`]: https://www.sqlite.org/c3ref/changes.html | |
| 519 | 572 | [`sqlite3_close_v2()`]: https://www.sqlite.org/c3ref/close.html | |
| 520 | 573 | [`sqlite3_create_function_v2()`]: https://www.sqlite.org/c3ref/create_function.html | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -144,10 +144,13 @@ | |||
| 144 | 144 | V(entry_type_string, "entryType") \ | |
| 145 | 145 | V(env_pairs_string, "envPairs") \ | |
| 146 | 146 | V(env_var_settings_string, "envVarSettings") \ | |
| 147 | + V(err_sqlite_error_string, "ERR_SQLITE_ERROR") \ | ||
| 148 | + V(errcode_string, "errcode") \ | ||
| 147 | 149 | V(errno_string, "errno") \ | |
| 148 | 150 | V(error_string, "error") \ | |
| 149 | - V(events, "events") \ | ||
| 151 | + V(errstr_string, "errstr") \ | ||
| 150 | 152 | V(events_waiting, "eventsWaiting") \ | |
| 153 | + V(events, "events") \ | ||
| 151 | 154 | V(exchange_string, "exchange") \ | |
| 152 | 155 | V(expire_string, "expire") \ | |
| 153 | 156 | V(exponent_string, "exponent") \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -42,6 +42,7 @@ using v8::Number; | |||
| 42 | 42 | using v8::Object; | |
| 43 | 43 | using v8::SideEffectType; | |
| 44 | 44 | using v8::String; | |
| 45 | + using v8::TryCatch; | ||
| 45 | 46 | using v8::Uint8Array; | |
| 46 | 47 | using v8::Value; | |
| 47 | 48 | ||
@@ -66,13 +67,14 @@ inline MaybeLocal<Object> CreateSQLiteError(Isolate* isolate, | |||
| 66 | 67 | const char* message) { | |
| 67 | 68 | Local<String> js_msg; | |
| 68 | 69 | Local<Object> e; | |
| 70 | + Environment* env = Environment::GetCurrent(isolate); | ||
| 69 | 71 | if (!String::NewFromUtf8(isolate, message).ToLocal(&js_msg) || | |
| 70 | 72 | !Exception::Error(js_msg) | |
| 71 | 73 | ->ToObject(isolate->GetCurrentContext()) | |
| 72 | 74 | .ToLocal(&e) || | |
| 73 | 75 | e->Set(isolate->GetCurrentContext(), | |
| 74 | - OneByteString(isolate, "code"), | ||
| 75 | - OneByteString(isolate, "ERR_SQLITE_ERROR")) | ||
| 76 | + env->code_string(), | ||
| 77 | + env->err_sqlite_error_string()) | ||
| 76 | 78 | .IsNothing()) { | |
| 77 | 79 | return MaybeLocal<Object>(); | |
| 78 | 80 | } | |
@@ -85,15 +87,14 @@ inline MaybeLocal<Object> CreateSQLiteError(Isolate* isolate, sqlite3* db) { | |||
| 85 | 87 | const char* errmsg = sqlite3_errmsg(db); | |
| 86 | 88 | Local<String> js_errmsg; | |
| 87 | 89 | Local<Object> e; | |
| 90 | + Environment* env = Environment::GetCurrent(isolate); | ||
| 88 | 91 | if (!String::NewFromUtf8(isolate, errstr).ToLocal(&js_errmsg) || | |
| 89 | 92 | !CreateSQLiteError(isolate, errmsg).ToLocal(&e) || | |
| 90 | 93 | e->Set(isolate->GetCurrentContext(), | |
| 91 | - OneByteString(isolate, "errcode"), | ||
| 94 | + env->errcode_string(), | ||
| 92 | 95 | Integer::New(isolate, errcode)) | |
| 93 | 96 | .IsNothing() || | |
| 94 | - e->Set(isolate->GetCurrentContext(), | ||
| 95 | - OneByteString(isolate, "errstr"), | ||
| 96 | - js_errmsg) | ||
| 97 | + e->Set(isolate->GetCurrentContext(), env->errstr_string(), js_errmsg) | ||
| 97 | 98 | .IsNothing()) { | |
| 98 | 99 | return MaybeLocal<Object>(); | |
| 99 | 100 | } | |
@@ -114,6 +115,19 @@ inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, const char* message) { | |||
| 114 | 115 | } | |
| 115 | 116 | } | |
| 116 | 117 | ||
| 118 | + inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, int errcode) { | ||
| 119 | + const char* errstr = sqlite3_errstr(errcode); | ||
| 120 | + | ||
| 121 | + Environment* env = Environment::GetCurrent(isolate); | ||
| 122 | + auto error = CreateSQLiteError(isolate, errstr).ToLocalChecked(); | ||
| 123 | + error | ||
| 124 | + ->Set(isolate->GetCurrentContext(), | ||
| 125 | + env->errcode_string(), | ||
| 126 | + Integer::New(isolate, errcode)) | ||
| 127 | + .ToChecked(); | ||
| 128 | + isolate->ThrowException(error); | ||
| 129 | + } | ||
| 130 | + | ||
| 117 | 131 | class UserDefinedFunction { | |
| 118 | 132 | public: | |
| 119 | 133 | explicit UserDefinedFunction(Environment* env, | |
@@ -731,11 +745,11 @@ void DatabaseSync::CreateSession(const FunctionCallbackInfo<Value>& args) { | |||
| 731 | 745 | ||
| 732 | 746 | // the reason for using static functions here is that SQLite needs a | |
| 733 | 747 | // function pointer | |
| 734 | - static std::function<int()> conflictCallback; | ||
| 748 | + static std::function<int(int)> conflictCallback; | ||
| 735 | 749 | ||
| 736 | 750 | static int xConflict(void* pCtx, int eConflict, sqlite3_changeset_iter* pIter) { | |
| 737 | 751 | if (!conflictCallback) return SQLITE_CHANGESET_ABORT; | |
| 738 | - return conflictCallback(); | ||
| 752 | + return conflictCallback(eConflict); | ||
| 739 | 753 | } | |
| 740 | 754 | ||
| 741 | 755 | static std::function<bool(std::string)> filterCallback; | |
@@ -773,15 +787,27 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) { | |||
| 773 | 787 | options->Get(env->context(), env->onconflict_string()).ToLocalChecked(); | |
| 774 | 788 | ||
| 775 | 789 | if (!conflictValue->IsUndefined()) { | |
| 776 | - if (!conflictValue->IsNumber()) { | ||
| 790 | + if (!conflictValue->IsFunction()) { | ||
| 777 | 791 | THROW_ERR_INVALID_ARG_TYPE( | |
| 778 | 792 | env->isolate(), | |
| 779 | - "The \"options.onConflict\" argument must be a number."); | ||
| 793 | + "The \"options.onConflict\" argument must be a function."); | ||
| 780 | 794 | return; | |
| 781 | 795 | } | |
| 782 | - | ||
| 783 | - int conflictInt = conflictValue->Int32Value(env->context()).FromJust(); | ||
| 784 | - conflictCallback = [conflictInt]() -> int { return conflictInt; }; | ||
| 796 | + Local<Function> conflictFunc = conflictValue.As<Function>(); | ||
| 797 | + conflictCallback = [env, conflictFunc](int conflictType) -> int { | ||
| 798 | + Local<Value> argv[] = {Integer::New(env->isolate(), conflictType)}; | ||
| 799 | + TryCatch try_catch(env->isolate()); | ||
| 800 | + Local<Value> result = | ||
| 801 | + conflictFunc->Call(env->context(), Null(env->isolate()), 1, argv) | ||
| 802 | + .FromMaybe(Local<Value>()); | ||
| 803 | + if (try_catch.HasCaught()) { | ||
| 804 | + try_catch.ReThrow(); | ||
| 805 | + return SQLITE_CHANGESET_ABORT; | ||
| 806 | + } | ||
| 807 | + constexpr auto invalid_value = -1; | ||
| 808 | + if (!result->IsInt32()) return invalid_value; | ||
| 809 | + return result->Int32Value(env->context()).FromJust(); | ||
| 810 | + }; | ||
| 785 | 811 | } | |
| 786 | 812 | ||
| 787 | 813 | if (options->HasOwnProperty(env->context(), env->filter_string()) | |
@@ -819,12 +845,16 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) { | |||
| 819 | 845 | xFilter, | |
| 820 | 846 | xConflict, | |
| 821 | 847 | nullptr); | |
| 848 | + if (r == SQLITE_OK) { | ||
| 849 | + args.GetReturnValue().Set(true); | ||
| 850 | + return; | ||
| 851 | + } | ||
| 822 | 852 | if (r == SQLITE_ABORT) { | |
| 853 | + // this is not an error, return false | ||
| 823 | 854 | args.GetReturnValue().Set(false); | |
| 824 | 855 | return; | |
| 825 | 856 | } | |
| 826 | - CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void()); | ||
| 827 | - args.GetReturnValue().Set(true); | ||
| 857 | + THROW_ERR_SQLITE_ERROR(env->isolate(), r); | ||
| 828 | 858 | } | |
| 829 | 859 | ||
| 830 | 860 | void DatabaseSync::EnableLoadExtension( | |
@@ -1662,6 +1692,12 @@ void DefineConstants(Local<Object> target) { | |||
| 1662 | 1692 | NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_OMIT); | |
| 1663 | 1693 | NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_REPLACE); | |
| 1664 | 1694 | NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_ABORT); | |
| 1695 | + | ||
| 1696 | + NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_DATA); | ||
| 1697 | + NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_NOTFOUND); | ||
| 1698 | + NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_CONFLICT); | ||
| 1699 | + NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_CONSTRAINT); | ||
| 1700 | + NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_FOREIGN_KEY); | ||
| 1665 | 1701 | } | |
| 1666 | 1702 | ||
| 1667 | 1703 | static void Initialize(Local<Object> target, | |
| Back | FazBrowse Home | New Git URL |
0 commit comments