@@ -23,6 +23,7 @@ namespace sqlite { |
23 | 23 | using v8::Array; |
24 | 24 | using v8::ArrayBuffer; |
25 | 25 | using v8::BackingStoreInitializationMode; |
| 26 | +using v8::BackingStoreOnFailureMode; |
26 | 27 | using v8::BigInt; |
27 | 28 | using v8::Boolean; |
28 | 29 | using v8::ConstructorBehavior; |
@@ -1719,6 +1720,136 @@ void DatabaseSync::Location(const FunctionCallbackInfo<Value>& args) { |
1719 | 1720 | } |
1720 | 1721 | } |
1721 | 1722 |
|
| 1723 | +void DatabaseSync::Serialize(const FunctionCallbackInfo<Value>& args) { |
| 1724 | + DatabaseSync* db; |
| 1725 | + ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); |
| 1726 | + Environment* env = Environment::GetCurrent(args); |
| 1727 | + THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); |
| 1728 | + |
| 1729 | + std::string db_name = "main"; |
| 1730 | + if (!args[0]->IsUndefined()) { |
| 1731 | + if (!args[0]->IsString()) { |
| 1732 | + THROW_ERR_INVALID_ARG_TYPE(env->isolate(), |
| 1733 | + "The \"dbName\" argument must be a string."); |
| 1734 | + return; |
| 1735 | + } |
| 1736 | + db_name = Utf8Value(env->isolate(), args[0].As<String>()).ToString(); |
| 1737 | + } |
| 1738 | + |
| 1739 | + sqlite3_int64 size = 0; |
| 1740 | + unsigned char* data = |
| 1741 | + sqlite3_serialize(db->connection_, db_name.c_str(), &size, 0); |
| 1742 | + |
| 1743 | + if (data == nullptr) { |
| 1744 | + if (size == 0) { |
| 1745 | + Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), 0); |
| 1746 | + args.GetReturnValue().Set(Uint8Array::New(ab, 0, 0)); |
| 1747 | + return; |
| 1748 | + } |
| 1749 | + THROW_ERR_SQLITE_ERROR(env->isolate(), db); |
| 1750 | + return; |
| 1751 | + } |
| 1752 | + |
| 1753 | + // V8 sandbox forbids external backing stores so allocate inside the |
| 1754 | + // sandbox and copy. Without sandbox wrap the output directly using |
| 1755 | + // sqlite3_free as the destructor to avoid the copy. |
| 1756 | +#ifdef V8_ENABLE_SANDBOX |
| 1757 | + auto free_data = OnScopeLeave([&] { sqlite3_free(data); }); |
| 1758 | + auto store = ArrayBuffer::NewBackingStore( |
| 1759 | + env->isolate(), |
| 1760 | + size, |
| 1761 | + BackingStoreInitializationMode::kUninitialized, |
| 1762 | + BackingStoreOnFailureMode::kReturnNull); |
| 1763 | + if (!store) { |
| 1764 | + THROW_ERR_MEMORY_ALLOCATION_FAILED(env); |
| 1765 | + return; |
| 1766 | + } |
| 1767 | + memcpy(store->Data(), data, size); |
| 1768 | + Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(store)); |
| 1769 | +#else |
| 1770 | + auto store = ArrayBuffer::NewBackingStore( |
| 1771 | + data, size, [](void* ptr, size_t, void*) { sqlite3_free(ptr); }, nullptr); |
| 1772 | + Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(store)); |
| 1773 | +#endif |
| 1774 | + |
| 1775 | + args.GetReturnValue().Set(Uint8Array::New(ab, 0, size)); |
| 1776 | +} |
| 1777 | + |
| 1778 | +void DatabaseSync::Deserialize(const FunctionCallbackInfo<Value>& args) { |
| 1779 | + DatabaseSync* db; |
| 1780 | + ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); |
| 1781 | + Environment* env = Environment::GetCurrent(args); |
| 1782 | + THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); |
| 1783 | + |
| 1784 | + if (!args[0]->IsUint8Array()) { |
| 1785 | + THROW_ERR_INVALID_ARG_TYPE(env->isolate(), |
| 1786 | + "The \"buffer\" argument must be a Uint8Array."); |
| 1787 | + return; |
| 1788 | + } |
| 1789 | + |
| 1790 | + Local<Uint8Array> input = args[0].As<Uint8Array>(); |
| 1791 | + size_t byte_length = input->ByteLength(); |
| 1792 | + |
| 1793 | + if (byte_length == 0) { |
| 1794 | + THROW_ERR_INVALID_ARG_VALUE(env, |
| 1795 | + "The \"buffer\" argument must not be empty."); |
| 1796 | + return; |
| 1797 | + } |
| 1798 | + |
| 1799 | + std::string db_name = "main"; |
| 1800 | + if (args.Length() > 1 && !args[1]->IsUndefined()) { |
| 1801 | + if (!args[1]->IsObject()) { |
| 1802 | + THROW_ERR_INVALID_ARG_TYPE(env->isolate(), |
| 1803 | + "The \"options\" argument must be an object."); |
| 1804 | + return; |
| 1805 | + } |
| 1806 | + |
| 1807 | + Local<Object> options = args[1].As<Object>(); |
| 1808 | + Local<String> db_name_key = FIXED_ONE_BYTE_STRING(env->isolate(), "dbName"); |
| 1809 | + Local<Value> db_name_value; |
| 1810 | + if (!options->Get(env->context(), db_name_key).ToLocal(&db_name_value)) { |
| 1811 | + return; |
| 1812 | + } |
| 1813 | + |
| 1814 | + if (!db_name_value->IsUndefined()) { |
| 1815 | + if (!db_name_value->IsString()) { |
| 1816 | + THROW_ERR_INVALID_ARG_TYPE( |
| 1817 | + env->isolate(), |
| 1818 | + "The \"options.dbName\" argument must be a string."); |
| 1819 | + return; |
| 1820 | + } |
| 1821 | + db_name = |
| 1822 | + Utf8Value(env->isolate(), db_name_value.As<String>()).ToString(); |
| 1823 | + } |
| 1824 | + } |
| 1825 | + |
| 1826 | + // sqlite3_malloc64 is required because SQLITE_DESERIALIZE_FREEONCLOSE |
| 1827 | + // transfers ownership to SQLite, which calls sqlite3_free() on close. |
| 1828 | + // See: https://www.sqlite.org/c3ref/deserialize.html |
| 1829 | + unsigned char* buf = |
| 1830 | + static_cast<unsigned char*>(sqlite3_malloc64(byte_length)); |
| 1831 | + if (buf == nullptr) { |
| 1832 | + THROW_ERR_MEMORY_ALLOCATION_FAILED(env); |
| 1833 | + return; |
| 1834 | + } |
| 1835 | + |
| 1836 | + input->CopyContents(buf, byte_length); |
| 1837 | + |
| 1838 | + db->FinalizeStatements(); |
| 1839 | + |
| 1840 | + int r = sqlite3_deserialize( |
| 1841 | + db->connection_, |
| 1842 | + db_name.c_str(), |
| 1843 | + buf, |
| 1844 | + byte_length, |
| 1845 | + byte_length, |
| 1846 | + SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE); |
| 1847 | + if (r != SQLITE_OK) { |
| 1848 | + THROW_ERR_SQLITE_ERROR(env->isolate(), db); |
| 1849 | + return; |
| 1850 | + } |
| 1851 | +} |
| 1852 | + |
1722 | 1853 | void DatabaseSync::AggregateFunction(const FunctionCallbackInfo<Value>& args) { |
1723 | 1854 | DatabaseSync* db; |
1724 | 1855 | ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); |
@@ -3766,6 +3897,8 @@ static void Initialize(Local<Object> target, |
3766 | 3897 | isolate, db_tmpl, "enableDefensive", DatabaseSync::EnableDefensive); |
3767 | 3898 | SetProtoMethod( |
3768 | 3899 | isolate, db_tmpl, "loadExtension", DatabaseSync::LoadExtension); |
| 3900 | + SetProtoMethod(isolate, db_tmpl, "serialize", DatabaseSync::Serialize); |
| 3901 | + SetProtoMethod(isolate, db_tmpl, "deserialize", DatabaseSync::Deserialize); |
3769 | 3902 | SetProtoMethod( |
3770 | 3903 | isolate, db_tmpl, "setAuthorizer", DatabaseSync::SetAuthorizer); |
3771 | 3904 | SetSideEffectFreeGetter(isolate, |
|