diff --git a/BASE.md b/BASE.md new file mode 100644 index 0000000..fd27fb9 --- /dev/null +++ b/BASE.md @@ -0,0 +1,157 @@ +# EJDB 2.0 + +[![Join Telegram](https://img.shields.io/badge/join-ejdb2%20telegram-0088cc.svg)](https://tlg.name/ejdb2) +[![license](https://img.shields.io/github/license/Softmotions/ejdb.svg)](https://github.com/Softmotions/ejdb/blob/master/LICENSE) +![maintained](https://img.shields.io/maintenance/yes/2021.svg) + +EJDB2 is an embeddable JSON database engine published under MIT license. + +[The Story of the IT-depression, birds and EJDB 2.0](https://medium.com/@adamansky/ejdb2-41670e80897c) + +* C11 API +* Single file database +* Online backups support +* 500K library size for Android +* [iOS](https://github.com/Softmotions/EJDB2Swift) / [Android](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_android/test) / [React Native](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_react_native) / [Flutter](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_flutter) integration +* Simple but powerful query language (JQL) as well as support of the following standards: + * [rfc6902](https://tools.ietf.org/html/rfc6902) JSON Patch + * [rfc7386](https://tools.ietf.org/html/rfc7386) JSON Merge patch + * [rfc6901](https://tools.ietf.org/html/rfc6901) JSON Path +* [Support of collection joins](#jql-collection-joins) +* Powered by [iowow.io](http://iowow.io) - The persistent key/value storage engine +* Provides HTTP REST/Websockets network endpoints with help of [facil.io](http://facil.io) +* JSON documents are stored in using fast and compact [binn](https://github.com/liteserver/binn) binary format + +--- +* [Native language bindings](#native-language-bindings) +* Supported platforms + * [OSX](#osx) + * [iOS](https://github.com/Softmotions/EJDB2Swift) + * [Linux](#linux) + * [Android](#android) + * [Windows](#windows) +* **[JQL query language](#jql)** + * [Grammar](#jql-grammar) + * [Quick into](#jql-quick-introduction) + * [Data modification](#jql-data-modification) + * [Projections](#jql-projections) + * [Collection joins](#jql-collection-joins) + * [Sorting](#jql-sorting) + * [Query options](#jql-options) +* [Indexes and performance](#jql-indexes-and-performance-tips) +* [Network API](#http-restwebsocket-api-endpoint) + * [HTTP API](#http-api) + * [Websockets API](#websocket-api) +* [C API](#c-api) +* [License](#license) +--- + +## EJDB2 platforms matrix + +| | Linux | macOS | iOS | Android | Windows | +| --- | --- | --- | --- | --- | --- | +| C library | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark:1 | +| NodeJS | :heavy_check_mark: | :heavy_check_mark: | | | :x:3 | +| DartVM | :heavy_check_mark: | :heavy_check_mark:2 | | | :x:3 | +| Flutter | | | :heavy_check_mark: | :heavy_check_mark: | | +| React Native | | | :x:4 | :heavy_check_mark: | | +| Swift | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | | +| Java | :heavy_check_mark: | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark:2 | + + +
`[1]` No HTTP/Websocket support [#257](https://github.com/Softmotions/ejdb/issues/257) +
`[2]` Binaries are not distributed with dart `pub.` You can build it [manually](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_node#how-build-it-manually) +
`[3]` Can be build, but needed a linkage with windows node/dart `libs`. +
`[4]` Porting in progress [#273](https://github.com/Softmotions/ejdb/issues/273) + + +## Native language bindings + +* [NodeJS](https://www.npmjs.com/package/ejdb2_node) +* [Dart](https://pub.dartlang.org/packages/ejdb2_dart) +* [Java](https://github.com/Softmotions/ejdb/blob/master/src/bindings/ejdb2_jni/README.md) +* [Android support](#android) +* [Swift | iOS](https://github.com/Softmotions/EJDB2Swift) +* [React Native](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_react_native) +* [Flutter](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_flutter) + +### Unofficial EJDB2 language bindings + +* .Net + * https://github.com/kmvi/ejdb2-csharp +* Haskell + * https://github.com/cescobaz/ejdb2haskell + * https://hackage.haskell.org/package/ejdb2-binding +* [Pharo](https://pharo.org) + * https://github.com/pharo-nosql/pharo-ejdb +* Lua + * https://github.com/chriku/ejdb-lua + +## Status + +* **EJDB 2.0 core engine is well tested and used in various heavily loaded deployments** +* Tested on `Linux` and `OSX` platforms. [Limited Windows support](./WINDOWS.md) +* Old EJDB 1.x version can be found in separate [ejdb_1.x](https://github.com/Softmotions/ejdb/tree/ejdb_1.x) branch. + We are not maintaining ejdb 1.x. + +## Use cases + +* Softmotions trading robots platform +* [Gimme - a social toy tokens exchange mobile application.](https://play.google.com/store/apps/details?id=com.softmotions.gimme) EJDB2 is used both on mobile and server sides. + +Are you using EJDB? [Let me know!](mailto:info@softmotions.com) + +## macOS / OSX + +EJDB2 code ported and tested on `High Sierra` / `Mojave` / `Catalina` + +See also [EJDB2 Swift binding](https://github.com/Softmotions/EJDB2Swift) for OSX, iOS and Linux + +``` +brew install ejdb +``` + +or + +``` +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +make install +``` + +## Linux +### Ubuntu/Debian +#### PPA repository + +```sh +sudo add-apt-repository ppa:adamansky/ejdb2 +sudo apt-get update +sudo apt-get install ejdb2 +``` + +#### Building debian packages + +cmake v3.15 or higher required + +```sh +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DPACKAGE_DEB=ON +make package +``` + +#### RPM based Linux distributions +```sh +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DPACKAGE_RPM=ON +make package +``` + +## Windows +EJDB2 can be cross-compiled for windows + +**Note:** HTTP/Websocket network API is disabled and not supported +on Windows until port of http://facil.io library (#257) + +Nodejs/Dart bindings not yet ported to Windows. + +**[Cross-compilation Guide for Windows](./WINDOWS.md)** diff --git a/BUILD.gn b/BUILD.gn new file mode 100644 index 0000000..647645e --- /dev/null +++ b/BUILD.gn @@ -0,0 +1,129 @@ +#Copyright (c) 2019-2021 Huawei Device Co., Ltd. +#Licensed under the Apache License, Version 2.0 (the "License"); +#you may not use this file except in compliance with the License. +#You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. + +import("//build/ohos.gni") + +iowow_src_dir = "//third_party/iowow/src" +action("copy_iowow_header") { + visibility = [ ":*" ] + script = "copy_iowow_header.py" + inputs = [] + outputs = [ + "$target_gen_dir/ejdb2/iowow/basedefs.h", + "$target_gen_dir/ejdb2/iowow/iowow.h", + "$target_gen_dir/ejdb2/iowow/iwarr.h", + "$target_gen_dir/ejdb2/iowow/iwbits.h", + "$target_gen_dir/ejdb2/iowow/iwconv.h", + "$target_gen_dir/ejdb2/iowow/iwdlsnr.h", + "$target_gen_dir/ejdb2/iowow/iwexfile.h", + "$target_gen_dir/ejdb2/iowow/iwfile.h", + "$target_gen_dir/ejdb2/iowow/iwfsmfile.h", + "$target_gen_dir/ejdb2/iowow/iwhmap.h", + "$target_gen_dir/ejdb2/iowow/iwkv.h", + "$target_gen_dir/ejdb2/iowow/iwlog.h", + "$target_gen_dir/ejdb2/iowow/iwp.h", + "$target_gen_dir/ejdb2/iowow/iwpool.h", + "$target_gen_dir/ejdb2/iowow/iwrdb.h", + "$target_gen_dir/ejdb2/iowow/iwsha2.h", + "$target_gen_dir/ejdb2/iowow/iwstree.h", + "$target_gen_dir/ejdb2/iowow/iwstw.h", + "$target_gen_dir/ejdb2/iowow/iwth.h", + "$target_gen_dir/ejdb2/iowow/iwtp.h", + "$target_gen_dir/ejdb2/iowow/iwutils.h", + "$target_gen_dir/ejdb2/iowow/iwuuid.h", + "$target_gen_dir/ejdb2/iowow/iwxstr.h", + "$target_gen_dir/ejdb2/iowow/murmur3.h", + ] + args = [ + "--src-dir", + rebase_path(iowow_src_dir), + "--dst-dir", + rebase_path("$target_gen_dir/ejdb2/iowow"), + ] +} + +config("ejdb_config") { + include_dirs = [ + "src", + "src/jbi", + "src/jbl", + "src/jql", + "src/util", + "$target_gen_dir", + ] + + cflags = [ + "-DNDEBUG", + "-O3", + "-Wall", + "-Wextra", + "-Wfatal-errors", + "-Wno-implicit-fallthrough", + "-Wno-missing-braces", + "-Wno-missing-field-initializers", + "-Wno-shorten-64-to-32", + "-Wno-sign-compare", + "-Wno-unknown-pragmas", + "-Wno-unused-function", + "-Wno-unused-parameter", + "-fPIC", + "-fsigned-char", + "-pedantic", + "-std=gnu11", + ] + + defines = [ + "IW_64", + "IW_API_EXPORTS", + + #"JB_HAVE_QSORT_R", + "JB_PTHREADS", + "_DEFAULT_SOURCE", + "_FILE_OFFSET_BITS=64", + "_LARGEFILE_SOURCE", + "_XOPEN_SOURCE=600", + ] +} + +ohos_shared_library("ejdb") { + configs = [ ":ejdb_config" ] + + public_configs = [ ":ejdb_config" ] + + sources = [ + "src/ejdb2.c", + "src/jbi/jbi_consumer.c", + "src/jbi/jbi_dup_scanner.c", + "src/jbi/jbi_full_scanner.c", + "src/jbi/jbi_pk_scanner.c", + "src/jbi/jbi_selection.c", + "src/jbi/jbi_sorter_consumer.c", + "src/jbi/jbi_uniq_scanner.c", + "src/jbi/jbi_util.c", + "src/jbl/binn.c", + "src/jbl/jbl.c", + "src/jbl/jbl_json.c", + "src/jql/jql.c", + "src/jql/jqp.c", + "src/util/lwre.c", + "src/util/utf8proc.c", + ] + + deps = [ + ":copy_iowow_header", + "//third_party/iowow:iowow", + ] + + part_name = "hiview" + subsystem_name = "hiviewdfx" +} diff --git a/CAPI.md b/CAPI.md new file mode 100644 index 0000000..08bbc39 --- /dev/null +++ b/CAPI.md @@ -0,0 +1,98 @@ +# C API + +EJDB can be embedded into any `C/C++` application. +`C API` documented in the following headers: + +* [ejdb.h](https://github.com/Softmotions/ejdb/blob/master/src/ejdb2.h) Main API functions +* [jbl.h](https://github.com/Softmotions/ejdb/blob/master/src/jbl/jbl.h) JSON documents management API +* [jql.h](https://github.com/Softmotions/ejdb/blob/master/src/jql/jql.h) Query building API + +Example application: +```c +#include + +#define CHECK(rc_) \ + if (rc_) { \ + iwlog_ecode_error3(rc_); \ + return 1; \ + } + +static iwrc documents_visitor(EJDB_EXEC *ctx, const EJDB_DOC doc, int64_t *step) { + // Print document to stderr + return jbl_as_json(doc->raw, jbl_fstream_json_printer, stderr, JBL_PRINT_PRETTY); +} + +int main() { + + EJDB_OPTS opts = { + .kv = { + .path = "example.db", + .oflags = IWKV_TRUNC + } + }; + EJDB db; // EJDB2 storage handle + int64_t id; // Document id placeholder + JQL q = 0; // Query instance + JBL jbl = 0; // Json document + + iwrc rc = ejdb_init(); + CHECK(rc); + + rc = ejdb_open(&opts, &db); + CHECK(rc); + + // First record + rc = jbl_from_json(&jbl, "{\"name\":\"Bianca\", \"age\":4}"); + RCGO(rc, finish); + rc = ejdb_put_new(db, "parrots", jbl, &id); + RCGO(rc, finish); + jbl_destroy(&jbl); + + // Second record + rc = jbl_from_json(&jbl, "{\"name\":\"Darko\", \"age\":8}"); + RCGO(rc, finish); + rc = ejdb_put_new(db, "parrots", jbl, &id); + RCGO(rc, finish); + jbl_destroy(&jbl); + + // Now execute a query + rc = jql_create(&q, "parrots", "/[age > :age]"); + RCGO(rc, finish); + + EJDB_EXEC ux = { + .db = db, + .q = q, + .visitor = documents_visitor + }; + + // Set query placeholder value. + // Actual query will be /[age > 3] + rc = jql_set_i64(q, "age", 0, 3); + RCGO(rc, finish); + + // Now execute the query + rc = ejdb_exec(&ux); + +finish: + jql_destroy(&q); + jbl_destroy(&jbl); + ejdb_close(&db); + CHECK(rc); + return 0; +} +``` + +Compile and run: +``` +gcc -std=gnu11 -Wall -pedantic -c -o example1.o example1.c +gcc -o example1 example1.o -lejdb2 + +./example1 +{ + "name": "Darko", + "age": 8 +}{ + "name": "Bianca", + "age": 4 +} +``` diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f5a20f7 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,204 @@ +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/") +include(CMakeToolsHelpers OPTIONAL) +set(DEB_CHANGELOG_REQUIRED ON) +set(DEB_CHANGELOG "${CMAKE_CURRENT_SOURCE_DIR}/Changelog") +unset(CHANGELOG_LAST_VERSION) +unset(CHANGELOG_LAST_MESSAGE) +include(DebChangelog) + +set(PROJECT_NAME "ejdb2") +project(${PROJECT_NAME} C) + +set(PROJECT_VENDOR "Softmotions (https://softmotions.com)") +set(PROJECT_WEBSITE "http://ejdb.org") +set(PROJECT_MAINTAINER "Anton Adamansky ") +set(PROJECT_DESCRIPTION_SUMMARY + "Embeddable JSON database engine with network support (EJDB2).") +set(PROJECT_DESCRIPTION + "Embeddable JSON database engine with network support (EJDB2).") +set(CHANGELOG_MESSAGE ${CHANGELOG_LAST_MESSAGE}) +set(PROJECT_PPA "ppa:adamansky/ejdb2") +set(PROJECT_PPA_USER "adamansky") +set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO") +set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "") + +set(PROJECT_VERSION_MAJOR ${CHANGELOG_LAST_VERSION_MAJOR}) +set(PROJECT_VERSION_MINOR ${CHANGELOG_LAST_VERSION_MINOR}) +set(PROJECT_VERSION_PATCH ${CHANGELOG_LAST_VERSION_PATCH}) +set(PROJECT_VERSION + ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}) +set(${PROJECT_NAME}_VERSION ${PROJECT_VERSION}) +set(${PROJECT_NAME}_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(${PROJECT_NAME}_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(${PROJECT_NAME}_VERSION_PATCH ${PROJECT_VERSION_PATCH}) + +option(BUILD_SHARED_LIBS "Build shared libraries" ON) +option(ENABLE_HTTP "Enable HTTP endpoint and standalone server (jbs)" ON) +option(BUILD_TESTS "Build test cases" OFF) +option(ASAN "Turn on address sanitizer" OFF) +option(BUILD_EXAMPLES "Build example projects" ON) +option(BUILD_BENCHMARKS "Build benchmarks" OFF) +option(BUILD_JNI_BINDING "Build Java native binding" OFF) +option(BUILD_ANDROID_LIBS "Build Android libs" OFF) +option(BUILD_DART_BINDING "Build Dart VM binding" OFF) +option(BUILD_NODEJS_BINDING "Build Node.js binding" OFF) +option(BUILD_REACT_NATIVE_BINDING "Build React Native binding" OFF) +option(BUILD_FLUTTER_BINDING "Build Flutter binding" OFF) +option(BUILD_SWIFT_BINDING "Build Swift binding" OFF) +option(PACKAGE_DEB "Build .deb instalation packages" OFF) +option(PACKAGE_RPM "Build .rpm instalation packages" OFF) +option(PACKAGE_TGZ "Build .tgz package archive" ON) +option(PACKAGE_ZIP "Build .zip package archive" ON) +option(ENABLE_PPA "Enable PPA package build" OFF) +option(UPLOAD_PPA "Upload debian packages to the launchpad ppa repository" OFF) + +set(PPA_DEBIAN_VERSION + "ppa1" + CACHE STRING "PPA version suffix for debian packages") + +set(PROJECT_PPA_DISTRIB_TARGET + "focal;bionic;xenial" + CACHE STRING "Ubuntu PPA distribution names") + +set(ANDROID_ABIS + "x86;x86_64;arm64-v8a;armeabi-v7a" + CACHE STRING "Android ABI list") + +set(ANDROID_AVD + "TestingAVD" + CACHE STRING "Android virtual device name for tests") + +set(CPACK_SET_DESTDIR ON) + +if(POLICY CMP0042) + cmake_policy(SET CMP0042 NEW) +endif(POLICY CMP0042) + +if(POLICY CMP0087) + cmake_policy(SET CMP0087 NEW) +endif(POLICY CMP0087) + +if(BUILD_REACT_NATIVE_BINDING + OR BUILD_FLUTTER_BINDING + OR BUILD_ANDROID_LIBS) + set(ENABLE_HTTP OFF) + set(BUILD_ANDROID_LIBS ON) +endif() + +if(ANDROID_ABI OR IOS) + set(ENABLE_HTTP OFF) +endif() + +if(DEFINED CMAKE_TOOLCHAIN_FILE) + get_filename_component(_CMAKE_TOOLCHAIN_FILE ${CMAKE_TOOLCHAIN_FILE} ABSOLUTE) + set(CMAKE_TOOLCHAIN_FILE ${_CMAKE_TOOLCHAIN_FILE}) +endif() + +include(GitRevision) +include(GNUInstallDirs) +include(ProjectUtils) + +macro_ensure_out_of_source_build( + "${CMAKE_PROJECT_NAME} requires an out of source build.") + +if(BUILD_TESTS) + include(CTest) + find_package(CUnit REQUIRED) +endif(BUILD_TESTS) + +if(UPLOAD_PPA) + set(ENABLE_PPA ON) + set(PACKAGE_DEB ON) +endif(UPLOAD_PPA) + +set(CPACK_GENERATORS) +if(PACKAGE_TGZ) + list(APPEND CPACK_GENERATORS "TGZ") +endif() +if(PACKAGE_ZIP) + list(APPEND CPACK_GENERATORS "ZIP") +endif() +if(PACKAGE_DEB) + list(APPEND CPACK_GENERATORS "DEB") +endif() +if(PACKAGE_RPM) + list(APPEND CPACK_GENERATORS "RPM") +endif() + +if(NOT + (CPACK_GENERATORS + AND (BUILD_JNI_BINDING + OR BUILD_DART_BINDING + OR BUILD_NODEJS_BINDING))) + set(DO_INSTALL_CORE ON) +endif() + +add_subdirectory(man) + +if(NOT ENABLE_PPA) + add_subdirectory(src) +endif() + +if(CPACK_GENERATORS) + set(CPACK_GENERATOR "${CPACK_GENERATORS}") + set(CPACK_SOURCE_IGNORE_FILES + "/src/bindings/ejdb2_android" + "/src/bindings/ejdb2_dart" + "/src/bindings/ejdb2_flutter" + "/src/bindings/ejdb2_node" + "/src/bindings/ejdb2_react_native" + "/src/bindings/ejdb2_swift" + "/mxe/" + "/build/" + "/target/" + "/tools/" + "/docker/" + "/node_modules/" + "/cmake-.*/" + "/Makefile$" + "hints\\\\.txt$" + "/\\\\.clang/" + "/\\\\.codelite/" + "/\\\\.git/" + "/\\\\.idea/" + "/\\\\.settings/" + "/\\\\.vscode/" + "\\\\.classpath$" + "\\\\.editorconfig$" + "\\\\.iml$" + "\\\\.ipr$" + "\\\\.log$" + "\\\\.mk$" + "\\\\.project$" + "\\\\.workspace$" + "\\\\.astylerc$" + "uncrustify\\\\.cfg") + set(PROJECT_ARCH "${CMAKE_SYSTEM_PROCESSOR}") + add_subdirectory(installer) +endif(CPACK_GENERATORS) + +message("${PROJECT_NAME} GIT_REVISION: ${GIT_REVISION}") +message("${PROJECT_NAME} CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}") +message("${PROJECT_NAME} CPACK_GENERATORS: ${CPACK_GENERATORS}") +message("${PROJECT_NAME} CMAKE_GENERATOR: ${CMAKE_GENERATOR}") + +if(MXE_HOME) + message("${PROJECT_NAME} MXE_HOME: ${MXE_HOME}") +endif() + +if(CMAKE_SYSTEM_NAME) + message("${PROJECT_NAME} CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") +endif() +message("${PROJECT_NAME} CPU: ${CMAKE_SYSTEM_PROCESSOR}") + +if(CMAKE_SIZEOF_VOID_P) + message("${PROJECT_NAME} SIZEOF *VOID: ${CMAKE_SIZEOF_VOID_P}") +endif() +message("${PROJECT_NAME} PROJECT: ${CHANGELOG_LAST_LINE}") + +if(CHANGELOG_MESSAGE) + message("${PROJECT_NAME} CHANGELOG_MESSAGE:\n ${CHANGELOG_MESSAGE}") +endif() diff --git a/Changelog b/Changelog new file mode 100644 index 0000000..2ff2c01 --- /dev/null +++ b/Changelog @@ -0,0 +1,457 @@ +ejdb2 (2.0.59) testing; urgency=medium + + * Fixed some uninitialized data issues with `jbl_clone()` + * Added ejdb_put_new_jbn() (ejdb2.h) + * jbs: Default database file name changed from db.jb to ejdb2.db + * Added jbl_from_json_printf_va(), jbn_from_json_printf_va() (jbl.h) + * Project code reformatted using uncrustify + * Updated copyright headers + * Removed dependency on some glibc specific features + * Added jbl_object_copy_to() (ejdb2.h) + * Added new convenient methods (ejdb2.h) + - ejdb_patch_jbn() + - ejdb_patch_jbl() + - ejdb_merge_or_put_jbn() + - ejdb_merge_or_put_jbl() + + -- Anton Adamansky Sun, 21 Mar 2021 22:17:40 +0700 + +ejdb2 (2.0.58) testing; urgency=medium + + * New versioning scheme is used for ejdb2 bindings. + * Fixed pub publish issues with dart and flutter bindings. + * Upgraded to iwkv v1.4.10 + + -- Anton Adamansky Tue, 22 Dec 2020 22:33:24 +0700 + +ejdb2 (2.0.57) testing; urgency=medium + + * Added new non standard JSON `swap` operation. + * bugfix: Incorrect behavior of JSON patch `move` operation. + * Added --trylock option to jbs + * jbs server CLI should be able to load access token from file (#219) + * bugfix: Updated to iowow v1.4.10 with fix https://github.com/Softmotions/iowow/issues/35 + * enhancement: EJDB2 Java binding improvements. + + -- Anton Adamansky Thu, 17 Dec 2020 23:12:37 +0700 + +ejdb2 (2.0.56) testing; urgency=medium + + * EJDB2 Java binding improvements. + * Added EJDB2 Maven example project and maven repository deployment. + + -- Anton Adamansky Thu, 10 Dec 2020 00:27:09 +0700 + +ejdb2 (2.0.55) testing; urgency=medium + + * EJDB2 Java binding now in Ubuntu ppa:adamansky/ejdb2 + * Build scripts refactoring. + * Reduced number of WS server threads to half of available CPU Cores. + + -- Anton Adamansky Sun, 06 Dec 2020 22:16:20 +0700 + +ejdb2 (2.0.54) testing; urgency=medium + + * CRITICAL: Fixed incorrect behavior of `_jbl_cmp_atomic_values` + which leads to inconsitent number indexes processing. + * Transfered `-DASAN` to `AddIOWOW` CMake module. + + -- Anton Adamansky Thu, 19 Nov 2020 23:33:44 +0700 + +ejdb2 (2.0.53) testing; urgency=medium + + * Upgraded to iowow v1.4.9 + * Eliminate duplicated documents from non-inique index scanning + * Add prefix matching operator ~ (#292) + + -- Anton Adamansky Wed, 18 Nov 2020 16:00:16 +0700 + +ejdb2 (2.0.52) testing; urgency=medium + + * Fixed serios bug concerning non-unique indexes, details: (#291) + * Dart binding: support of the latest Dart pub + * Added `jbn_add_item_null()` (jbl.h) + * SIGSEGV, Segmentation fault (#287) + * Upgraded to facil.io 0.7.5 + * Code cleanup + + -- Anton Adamansky Sun, 08 Nov 2020 22:04:58 +0700 + +ejdb2 (2.0.51) testing; urgency=medium + + * Upgraded to iowow v1.4.7 + * Added `jbl_from_json_printf()` and `jbn_from_json_printf()` (jbl.h) + * Added JQL `upsert` operation + * Typo fixed (jbl.c) + * `jbl_get_str()` now returns `const char*` (jbl.h) + * Added `jbl_set_user_data()` (jbl.h) + * Removed disable int compression feature from binn implementation (binn.c) + + -- Anton Adamansky Mon, 21 Sep 2020 12:45:01 +0700 + +ejdb2 (2.0.50) testing; urgency=medium + + * Better floating point numbers comparison in `jbn_compare_nodes()` (jbl.h) + * Fixed error in `jbn_copy_path()` if some source property doesn't exist (jbl.h) + * Added `jbn_length()` (jbl.h) + * Added `lwre.h` to set of public headers + * Added `node_out` for every `jbn_add_item_X()` (jbl.h) + * Fixed compilation error on clang-10 + * Fixed ejdb2_jni to be java-8 compatible + * Fixed ejdb2_jni to be headless JDK compatible + + -- Anton Adamansky Wed, 24 Jun 2020 20:42:46 +0700 + +ejdb2 (2.0.49) testing; urgency=medium + + * Added ability to specify array of primary key values in `/=:?` query clause. + + -- Anton Adamansky Sun, 17 May 2020 01:21:02 +0700 + +ejdb2 (2.0.48) testing; urgency=medium + + * Implemented collection JOINs (#280) + + -- Anton Adamansky Fri, 15 May 2020 14:22:08 +0700 + +ejdb2 (2.0.47) testing; urgency=medium + + * Added ability to specify constraints on internal primary key in query (#281) + * Added `jbn_copy_paths` (jbl.h) + + -- Anton Adamansky Fri, 08 May 2020 23:23:53 +0700 + +ejdb2 (2.0.46) testing; urgency=medium + + * Upgraded to iowow v1.4.4 + * Added `jbn_paths_compare()` (jbl.h) + * Added `no_src_clone` parameter `jbn_copy_path` (jbl.h) + + -- Anton Adamansky Sat, 02 May 2020 19:43:36 +0700 + +ejdb2 (2.0.45) testing; urgency=medium + + * Fixed errors found by PVS studio + * Added two variants of `jbn_detach` (jbl.h) + * Added non standard JSON patch operation: `add_create` (jbl.h) + * Added `jbl_from_node` (jbl.h) + * Added `jbn_copy_path` (jbl.h) + * Added `jbn_add_item_obj` (jbl.h) + * Added `jbn_add_item_arr` (jbl.h) + * Minor refactoring of jbl module + * Added a set of jbn_add_x (jbl.h) + + -- Anton Adamansky Fri, 24 Apr 2020 02:20:32 +0700 + +ejdb2 (2.0.44) testing; urgency=medium + + * Fixed missing parent pointer link in `_jbl_add_item` (jbl_json.c). + * Added `jbn_clone()` (jbl.h) + * Added checks for max nesting level of JSON objects, 1000 by default. + * Added `jbl_clone(JBL src, JBL *targetp)` (jbl.h) + * Added `jbl_merge_patch_jbl(JBL jbl, JBL patch)` (jbl.h) + * Added `jbl_set_string_printf()` (jbl.h) + * Added `jbl_structure_size()` (jbl.h) + * Better error handling of `kh_put()` + * Added `jbl_clone_into_pool(JBL src, IWPOOL *pool, JBL *targetp)` (jbl.h) + * Added jbn_path_compare, jbn_path_compare_str, jbn_path_compare_bool, + jbn_path_compare_i64, jbn_path_compare_f64 (jbl.h) + + -- Anton Adamansky Mon, 20 Apr 2020 16:24:41 +0700 + +ejdb2 (2.0.43) testing; urgency=medium + + * Upgraded to iowow v1.4.1 + + -- Anton Adamansky Sat, 07 Mar 2020 23:39:40 +0700 + +ejdb2 (2.0.42) testing; urgency=medium + + * Upgraded to iowow v1.4.0 + + -- Anton Adamansky Sat, 07 Mar 2020 11:16:10 +0700 + +ejdb2 (2.0.41) testing; urgency=medium + + * Fixed race condition on database open on slow devices + + -- Anton Adamansky Tue, 18 Feb 2020 18:55:57 +0700 + +ejdb2 (2.0.40) testing; urgency=medium + + * Added `jbl_object_get_i64`, `jbl_object_get_f64`, + `jbl_object_get_bool`, `jbl_object_get_str` + * Minor fixes in index selection rules + * Added `jbl_type_t jbl_object_get_type(JBL jbl, const char *key)` (jbl.h) + * Upgraded to iowow 1.3.37 + + -- Anton Adamansky Mon, 17 Feb 2020 23:38:24 +0700 + +ejdb2 (2.0.39) testing; urgency=medium + + * Added iwrc jbn_at(JBL_NODE node, const char *path, JBL_NODE *res) + * Added iwrc jbn_at2(JBL_NODE node, JBL_PTR jp, JBL_NODE *res) + * Added `jbl_set_int64`, `jbl_set_f64`, `jbl_set_string`, `jbl_set_nested` (jbl.h) + * Added `jbl_set_bool`, `jbl_set_null` (jbl.h) + * Added `jbl_set_empty_array`, `jbl_set_empty_object` (jbl.h) + * Safer `jql_destroy` (jql.h) + * Added `jql_set_json_jbl` (jql.h) + * Added `ejdb_count` (ejdb2.h) + * Fixed `_jbl_create_patch` issues + + -- Anton Adamansky Wed, 22 Jan 2020 16:13:20 +0700 + +ejdb2 (2.0.38) testing; urgency=medium + + * FIXED: In the case of apply/projection `ejdb_list` returned old (not updated) documents + * Added `iwrc ejdb_get_iwkv(EJDB db, IWKV *kvp)` (ejdb2.h) + * Added non standart JSON patch extension: `increment` + + -- Anton Adamansky Fri, 17 Jan 2020 18:23:56 +0700 + +ejdb2 (2.0.37) testing; urgency=medium + + * Upgraded to iowow v1.3.35 + * Fixed wal related durability issue + + -- Anton Adamansky Wed, 15 Jan 2020 13:19:06 +0700 + +ejdb2 (2.0.36) testing; urgency=medium + + * Implemented Swift language binding #267 + * Ported to iOS + * Upgraded to iowow 1.3.34 + * Upgraded to facil.io 0.7.4 + * Improved ejdb2.h documentation + + -- Anton Adamansky Mon, 06 Jan 2020 23:20:38 +0700 + +ejdb2 (2.0.35) testing; urgency=medium + + * FIXED: JQL apply ignores value set by placeholder (#269) + + -- Anton Adamansky Tue, 10 Dec 2019 21:12:43 +0700 + +ejdb2 (2.0.34) testing; urgency=medium + + * Reduced library size by 400K: stripped off unused utf8proc data + + -- Anton Adamansky Thu, 05 Dec 2019 12:01:59 +0700 + +ejdb2 (2.0.33) testing; urgency=medium + + * Added ejdb_merge_or_put atomic method #268 + * Added getOrNull to node/react bindings + + -- Anton Adamansky Wed, 04 Dec 2019 17:34:01 +0700 + +ejdb2 (2.0.32) testing; urgency=medium + + * Upgraded to iowow v1.3.31 + + -- Anton Adamansky Wed, 04 Dec 2019 17:33:20 +0700 + +ejdb2 (2.0.31) testing; urgency=medium + + * Upgraded to iowow v1.3.30 + + -- Anton Adamansky Tue, 19 Nov 2019 20:40:29 +0700 + +ejdb2 (2.0.30) testing; urgency=medium + + * Upgraded to iowow v1.3.29 + + -- Anton Adamansky Fri, 15 Nov 2019 13:55:03 +0700 + +ejdb2 (2.0.29) testing; urgency=medium + + * Upgraded to iowow v1.3.27 + * React Native binding implemented (Android) + + -- Anton Adamansky Mon, 04 Nov 2019 21:52:07 +0700 + +ejdb2 (2.0.28) testing; urgency=medium + + * Upgraded to facil.io v0.7.3 + * JBS server functions removed from ejdb2 target libs + + -- Anton Adamansky Fri, 27 Sep 2019 18:26:57 +0700 + +ejdb2 (2.0.27) testing; urgency=medium + + * Upgraded to iowow v1.3.25 with critical fixes + + -- Anton Adamansky Thu, 29 Aug 2019 12:46:41 +0700 + +ejdb2 (2.0.26) testing; urgency=medium + + * Upgraded to iowow v1.3.24 + + -- Anton Adamansky Thu, 22 Aug 2019 02:16:50 +0700 + +ejdb2 (2.0.25) testing; urgency=medium + + * ejdb2_dart: Fixed memory leak in dart binding + * Upgraded to iowow 1.3.23 + + -- Anton Adamansky Sat, 17 Aug 2019 21:27:29 +0700 + +ejdb2 (2.0.24) testing; urgency=medium + + * Strip shared libraries for release builds. + + -- Anton Adamansky Thu, 25 Jul 2019 21:09:00 +0700 + +ejdb2 (2.0.23) testing; urgency=medium + + * BUG: Fixed incorrect behavior of `jbi_ftoa` + + -- Anton Adamansky Fri, 19 Jul 2019 05:55:16 +0700 + +ejdb2 (2.0.22) testing; urgency=medium + + * Limited windows support without HTTP/Websocket network API (#237) + * `static_assert` is set to `_Static_assert` if not defined + * All uses of `iwftoa` replaced by `jbi_ftoa` + + -- Anton Adamansky Thu, 18 Jul 2019 19:27:26 +0700 + +ejdb2 (2.0.21) testing; urgency=medium + + * Added `ejdb_rename_collection` method (#254) + + -- Anton Adamansky Fri, 12 Jul 2019 12:39:32 +0700 + +ejdb2 (2.0.20) testing; urgency=medium + + * Node.js binding (#248) + * Added git revision hash getter `ejdb_git_revision()` (ejdb2.h) + * Better error reporting on failed memory allocations + + -- Anton Adamansky Wed, 03 Jul 2019 19:36:46 +0700 + +ejdb2 (2.0.19) testing; urgency=medium + + * Upgraded to iowow v1.3.20 with critical fixes + + -- Anton Adamansky Thu, 13 Jun 2019 22:04:27 +0700 + +ejdb2 (2.0.18) testing; urgency=medium + + * Limit one time file allocation step to 2G iowow v1.3.18 + * Added Docker image (#249) + * Better qsort_t detection, build ok with `musl` + + -- Anton Adamansky Wed, 12 Jun 2019 16:48:57 +0700 + +ejdb2 (2.0.17) testing; urgency=medium + + * Added `inverse` JQL query option. + + -- Anton Adamansky Wed, 05 Jun 2019 23:15:09 +0700 + +ejdb2 (2.0.16) testing; urgency=medium + + * Allowed dash and underscope chars in collection names specified in query: `[@collection_name]/query`. + * Fixed minor typo in ejdb2_jni/src/CMakeLists.txt + + -- Anton Adamansky Wed, 05 Jun 2019 19:14:38 +0700 + +ejdb2 (2.0.15) testing; urgency=medium + + * Security: a heap-overflow vulnerability was fixed in the WebSocket parser of facil.io library + + -- Anton Adamansky Mon, 27 May 2019 13:33:03 +0700 + +ejdb2 (2.0.14) testing; urgency=medium + + * Android support + * Test cases for Android JNI binding + + -- Anton Adamansky Fri, 17 May 2019 00:57:11 +0700 +ejdb2 (2.0.13) testing; urgency=medium + + * Upgraded to iowow_1.3.17 with critical fixes + + -- Anton Adamansky Fri, 03 May 2019 18:27:10 +0700 + +ejdb2 (2.0.12) testing; urgency=medium + + * Upgraded to iowow_1.3.16 with critical fixes + + -- Anton Adamansky Fri, 03 May 2019 11:59:56 +0700 + +ejdb2 (2.0.11) testing; urgency=medium + + * Upgraded to iowow_1.3.15 with critical fixes + + -- Anton Adamansky Wed, 01 May 2019 23:34:20 +0700 + +ejdb2 (2.0.10) testing; urgency=medium + + * CRITICAL: Fixed unexpected database file truncation and data loss on close. + * Upgraded to iowow 1.3.14 + + -- Anton Adamansky Sat, 27 Apr 2019 01:12:15 +0700 + +ejdb2 (2.0.9) testing; urgency=medium + + * Upgraded to iowow 1.3.13 + * Building Native JNI binding on Android + * ejdb2_dart: Added notFound,invalidQuery to EJDB2Error + + -- Anton Adamansky Thu, 25 Apr 2019 17:47:16 +0700 + +ejdb2 (2.0.8) testing; urgency=medium + + * Remove redundant memory coping in ejdb_put() + + -- Anton Adamansky Sun, 21 Apr 2019 14:38:08 +0700 + +ejdb2 (2.0.7) testing; urgency=medium + + * Fixed deadlock condition with OSX pthread rwlocks. + * Phreads `PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP` is used by default + + -- Anton Adamansky Sun, 21 Apr 2019 12:20:27 +0700 + +ejdb2 (2.0.6) testing; urgency=medium + + * Java JNI binding implemented (#243) + * Stability: `jql_create` takes own copy of `coll` argument + * Documentation improvements + + -- Anton Adamansky Fri, 19 Apr 2019 17:30:12 +0700 + +ejdb2 (2.0.5) testing; urgency=medium + + * Library ported to OSX + * Library ported to Android NDK (#244) + + -- Anton Adamansky Mon, 15 Apr 2019 11:28:30 +0700 + +ejdb2 (2.0.4) testing; urgency=medium + + * Landed DartVM binding https://pub.dartlang.org/packages/ejdb2_dart + * Minors API enhancements + + -- Anton Adamansky Tue, 09 Apr 2019 01:55:42 +0700 + +ejdb2 (2.0.3) testing; urgency=medium + + * Fixed #238 Disable network access for PPA Lunchpad builds + * Removed gcc `-fvisibility=hidden` from shared lib flag + + -- Anton Adamansky Wed, 03 Apr 2019 17:39:52 +0700 + +ejdb2 (2.0.1) testing; urgency=medium + + * Because we are static linked to iowow its header files included under `include/ejdb2/iowow` + + -- Anton Adamansky Tue, 02 Apr 2019 12:38:32 +0700 + +ejdb2 (2.0.0) testing; urgency=medium + + * Initial beta release + + -- Anton Adamansky Mon, 01 Apr 2019 20:21:27 +0700 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5319d1e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2012-2021 Softmotions Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.en.md b/README.en.md deleted file mode 100644 index 9a2404b..0000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# third_party_ejdb - -#### Description -{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index e21eb7c..dff074c 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,1345 @@ -# third_party_ejdb +# EJDB 2.0 -#### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} +[![Join Telegram](https://img.shields.io/badge/join-ejdb2%20telegram-0088cc.svg)](https://tlg.name/ejdb2) +[![license](https://img.shields.io/github/license/Softmotions/ejdb.svg)](https://github.com/Softmotions/ejdb/blob/master/LICENSE) +![maintained](https://img.shields.io/maintenance/yes/2021.svg) -#### 软件架构 -软件架构说明 +EJDB2 is an embeddable JSON database engine published under MIT license. + +[The Story of the IT-depression, birds and EJDB 2.0](https://medium.com/@adamansky/ejdb2-41670e80897c) + +* C11 API +* Single file database +* Online backups support +* 500K library size for Android +* [iOS](https://github.com/Softmotions/EJDB2Swift) / [Android](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_android/test) / [React Native](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_react_native) / [Flutter](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_flutter) integration +* Simple but powerful query language (JQL) as well as support of the following standards: + * [rfc6902](https://tools.ietf.org/html/rfc6902) JSON Patch + * [rfc7386](https://tools.ietf.org/html/rfc7386) JSON Merge patch + * [rfc6901](https://tools.ietf.org/html/rfc6901) JSON Path +* [Support of collection joins](#jql-collection-joins) +* Powered by [iowow.io](http://iowow.io) - The persistent key/value storage engine +* Provides HTTP REST/Websockets network endpoints with help of [facil.io](http://facil.io) +* JSON documents are stored in using fast and compact [binn](https://github.com/liteserver/binn) binary format + +--- +* [Native language bindings](#native-language-bindings) +* Supported platforms + * [OSX](#osx) + * [iOS](https://github.com/Softmotions/EJDB2Swift) + * [Linux](#linux) + * [Android](#android) + * [Windows](#windows) +* **[JQL query language](#jql)** + * [Grammar](#jql-grammar) + * [Quick into](#jql-quick-introduction) + * [Data modification](#jql-data-modification) + * [Projections](#jql-projections) + * [Collection joins](#jql-collection-joins) + * [Sorting](#jql-sorting) + * [Query options](#jql-options) +* [Indexes and performance](#jql-indexes-and-performance-tips) +* [Network API](#http-restwebsocket-api-endpoint) + * [HTTP API](#http-api) + * [Websockets API](#websocket-api) +* [C API](#c-api) +* [License](#license) +--- + +## EJDB2 platforms matrix + +| | Linux | macOS | iOS | Android | Windows | +| --- | --- | --- | --- | --- | --- | +| C library | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark:1 | +| NodeJS | :heavy_check_mark: | :heavy_check_mark: | | | :x:3 | +| DartVM | :heavy_check_mark: | :heavy_check_mark:2 | | | :x:3 | +| Flutter | | | :heavy_check_mark: | :heavy_check_mark: | | +| React Native | | | :x:4 | :heavy_check_mark: | | +| Swift | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | | +| Java | :heavy_check_mark: | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark:2 | -#### 安装教程 - -1. xxxx -2. xxxx -3. xxxx - -#### 使用说明 - -1. xxxx -2. xxxx -3. xxxx - -#### 参与贡献 - -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +
`[1]` No HTTP/Websocket support [#257](https://github.com/Softmotions/ejdb/issues/257) +
`[2]` Binaries are not distributed with dart `pub.` You can build it [manually](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_node#how-build-it-manually) +
`[3]` Can be build, but needed a linkage with windows node/dart `libs`. +
`[4]` Porting in progress [#273](https://github.com/Softmotions/ejdb/issues/273) -#### 特技 +## Native language bindings + +* [NodeJS](https://www.npmjs.com/package/ejdb2_node) +* [Dart](https://pub.dartlang.org/packages/ejdb2_dart) +* [Java](https://github.com/Softmotions/ejdb/blob/master/src/bindings/ejdb2_jni/README.md) +* [Android support](#android) +* [Swift | iOS](https://github.com/Softmotions/EJDB2Swift) +* [React Native](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_react_native) +* [Flutter](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_flutter) + +### Unofficial EJDB2 language bindings + +* .Net + * https://github.com/kmvi/ejdb2-csharp +* Haskell + * https://github.com/cescobaz/ejdb2haskell + * https://hackage.haskell.org/package/ejdb2-binding +* [Pharo](https://pharo.org) + * https://github.com/pharo-nosql/pharo-ejdb +* Lua + * https://github.com/chriku/ejdb-lua + +## Status + +* **EJDB 2.0 core engine is well tested and used in various heavily loaded deployments** +* Tested on `Linux` and `OSX` platforms. [Limited Windows support](./WINDOWS.md) +* Old EJDB 1.x version can be found in separate [ejdb_1.x](https://github.com/Softmotions/ejdb/tree/ejdb_1.x) branch. + We are not maintaining ejdb 1.x. + +## Use cases + +* Softmotions trading robots platform +* [Gimme - a social toy tokens exchange mobile application.](https://play.google.com/store/apps/details?id=com.softmotions.gimme) EJDB2 is used both on mobile and server sides. + +Are you using EJDB? [Let me know!](mailto:info@softmotions.com) + +## macOS / OSX + +EJDB2 code ported and tested on `High Sierra` / `Mojave` / `Catalina` + +See also [EJDB2 Swift binding](https://github.com/Softmotions/EJDB2Swift) for OSX, iOS and Linux + +``` +brew install ejdb +``` + +or + +``` +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +make install +``` + +## Linux +### Ubuntu/Debian +#### PPA repository + +```sh +sudo add-apt-repository ppa:adamansky/ejdb2 +sudo apt-get update +sudo apt-get install ejdb2 +``` + +#### Building debian packages + +cmake v3.15 or higher required + +```sh +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DPACKAGE_DEB=ON +make package +``` + +#### RPM based Linux distributions +```sh +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DPACKAGE_RPM=ON +make package +``` + +## Windows +EJDB2 can be cross-compiled for windows + +**Note:** HTTP/Websocket network API is disabled and not supported +on Windows until port of http://facil.io library (#257) + +Nodejs/Dart bindings not yet ported to Windows. + +**[Cross-compilation Guide for Windows](./WINDOWS.md)** + + + +# Android + +* [Flutter binding](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_flutter) +* [React Native binding](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_react_native) + +## Sample Android application + +* https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_android/test + +* https://github.com/Softmotions/ejdb_android_todo_app + + +# JQL + +EJDB query language (JQL) syntax inspired by ideas behind XPath and Unix shell pipes. +It designed for easy querying and updating sets of JSON documents. + +## JQL grammar + +JQL parser created created by +[peg/leg — recursive-descent parser generators for C](http://piumarta.com/software/peg/) Here is the formal parser grammar: https://github.com/Softmotions/ejdb/blob/master/src/jql/jqp.leg + +## Non formal JQL grammar adapted for brief overview + +Notation used below is based on SQL syntax description: + +Rule | Description +--- | --- +`' '` | String in single quotes denotes unquoted string literal as part of query. +{ a | b } | Curly brackets enclose two or more required alternative choices, separated by vertical bars. +[ ] | Square brackets indicate an optional element or clause. Multiple elements or clauses are separated by vertical bars. +| | Vertical bars separate two or more alternative syntax elements. +... | Ellipses indicate that the preceding element can be repeated. The repetition is unlimited unless otherwise indicated. +( ) | Parentheses are grouping symbols. +Unquoted word in lower case| Denotes semantic of some query part. For example: `placeholder_name` - name of any placeholder. +``` +QUERY = FILTERS [ '|' APPLY ] [ '|' PROJECTIONS ] [ '|' OPTS ]; + +STR = { quoted_string | unquoted_string }; + +JSONVAL = json_value; + +PLACEHOLDER = { ':'placeholder_name | '?' } + +FILTERS = FILTER [{ and | or } [ not ] FILTER]; + + FILTER = [@collection_name]/NODE[/NODE]...; + + NODE = { '*' | '**' | NODE_EXPRESSION | STR }; + + NODE_EXPRESSION = '[' NODE_EXPR_LEFT OP NODE_EXPR_RIGHT ']' + [{ and | or } [ not ] NODE_EXPRESSION]...; + + OP = [ '!' ] { '=' | '>=' | '<=' | '>' | '<' | ~ } + | [ '!' ] { 'eq' | 'gte' | 'lte' | 'gt' | 'lt' } + | [ not ] { 'in' | 'ni' | 're' }; + + NODE_EXPR_LEFT = { '*' | '**' | STR | NODE_KEY_EXPR }; + + NODE_KEY_EXPR = '[' '*' OP NODE_EXPR_RIGHT ']' + + NODE_EXPR_RIGHT = JSONVAL | STR | PLACEHOLDER + +APPLY = { 'apply' | 'upsert' } { PLACEHOLDER | json_object | json_array } | 'del' + +OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }... + + ORDERBY = { 'asc' | 'desc' } PLACEHOLDER | json_path + +PROJECTIONS = PROJECTION [ {'+' | '-'} PROJECTION ] + + PROJECTION = 'all' | json_path + +``` + +* `json_value`: Any valid JSON value: object, array, string, bool, number. +* `json_path`: Simplified JSON pointer. Eg.: `/foo/bar` or `/foo/"bar with spaces"/` +* `*` in context of `NODE`: Any JSON object key name at particular nesting level. +* `**` in context of `NODE`: Any JSON object key name at arbitrary nesting level. +* `*` in context of `NODE_EXPR_LEFT`: Key name at specific level. +* `**` in context of `NODE_EXPR_LEFT`: Nested array value of array element under specific key. + +## JQL quick introduction + +Lets play with some very basic data and queries. +For simplicity we will use ejdb websocket network API which provides us a kind of interactive CLI. The same job can be done using pure `C` API too (`ejdb2.h jql.h`). + +NOTE: Take a look into [JQL test cases](https://github.com/Softmotions/ejdb/blob/master/src/jql/tests/jql_test1.c) for more examples. + +```json +{ + "firstName": "John", + "lastName": "Doe", + "age": 28, + "pets": [ + {"name": "Rexy rex", "kind": "dog", "likes": ["bones", "jumping", "toys"]}, + {"name": "Grenny", "kind": "parrot", "likes": ["green color", "night", "toys"]} + ] +} +``` +Save json as `sample.json` then upload it the `family` collection: + +```sh +# Start HTTP/WS server protected by some access token +./jbs -a 'myaccess01' +8 Mar 16:15:58.601 INFO: HTTP/WS endpoint at localhost:9191 +``` + +Server can be accessed using HTTP or Websocket endpoint. [More info](https://github.com/Softmotions/ejdb/blob/master/src/jbr/README.md) + +```sh +curl -d '@sample.json' -H'X-Access-Token:myaccess01' -X POST http://localhost:9191/family +``` + +We can play around using interactive [wscat](https://www.npmjs.com/package/@softmotions/wscat) websocket client. + +```sh +wscat -H 'X-Access-Token:myaccess01' -c http://localhost:9191 +connected (press CTRL+C to quit) +> k info +< k { + "version": "2.0.0", + "file": "db.jb", + "size": 8192, + "collections": [ + { + "name": "family", + "dbid": 3, + "rnum": 1, + "indexes": [] + } + ] +} + +> k get family 1 +< k 1 { + "firstName": "John", + "lastName": "Doe", + "age": 28, + "pets": [ + { + "name": "Rexy rex", + "kind": "dog", + "likes": [ + "bones", + "jumping", + "toys" + ] + }, + { + "name": "Grenny", + "kind": "parrot", + "likes": [ + "green color", + "night", + "toys" + ] + } + ] +} +``` + +Note about the `k` prefix before every command; It is an arbitrary key chosen by client and designated to identify particular +websocket request, this key will be returned with response to request and allows client to +identify that response for his particular request. [More info](https://github.com/Softmotions/ejdb/blob/master/src/jbr/README.md) + +Query command over websocket has the following format: + +``` + query +``` + +So we will consider only `` part in this document. + +### Get all elements in collection +``` +k query family /* +``` +or +``` +k query family /** +``` +or specify collection name in query explicitly +``` +k @family/* +``` + +We can execute query by HTTP `POST` request +``` +curl --data-raw '@family/[firstName = John]' -H'X-Access-Token:myaccess01' -X POST http://localhost:9191 + +1 {"firstName":"John","lastName":"Doe","age":28,"pets":[{"name":"Rexy rex","kind":"dog","likes":["bones","jumping","toys"]},{"name":"Grenny","kind":"parrot","likes":["green color","night","toys"]}]} +``` + +### Set the maximum number of elements in result set + +``` +k @family/* | limit 10 +``` + +### Get documents where specified json path exists + +Element at index `1` exists in `likes` array within a `pets` sub-object +``` +> k query family /pets/*/likes/1 +< k 1 {"firstName":"John"... +``` + +Element at index `1` exists in `likes` array at any `likes` nesting level +``` +> k query family /**/likes/1 +< k 1 {"firstName":"John"... +``` + +**From this point and below I will omit websocket specific prefix `k query family` and +consider only JQL queries.** + + +### Get documents by primary key + +In order to get documents by primary key the following options are available: + +1. Use API call `ejdb_get()` + ```ts + const doc = await db.get('users', 112); + ``` + +1. Use the special query construction: `/=:?` or `@collection/=:?` + +Get document from `users` collection with primary key `112` +``` +> k @users/=112 +``` + +Update tags array for document in `jobs` collection (TypeScript): +```ts + await db.createQuery('@jobs/ = :? | apply :? | count') + .setNumber(0, id) + .setJSON(1, { tags }) + .completionPromise(); +``` + +Array of primary keys can also be used for matching: + +```ts + await db.createQuery('@jobs/ = :?| apply :? | count') + .setJSON(0, [23, 1, 2]) + .setJSON(1, { tags }) + .completionPromise(); +``` + +### Matching JSON entry values + +Below is a set of self explaining queries: + +``` +/pets/*/[name = "Rexy rex"] + +/pets/*/[name eq "Rexy rex"] + +/pets/*/[name = "Rexy rex" or name = Grenny] +``` +Note about quotes around words with spaces. + +Get all documents where owner `age` greater than `20` and have some pet who like `bones` or `toys` +``` +/[age > 20] and /pets/*/likes/[** in ["bones", "toys"]] +``` +Here `**` denotes some element in `likes` array. + +`ni` is the inverse operator to `in`. +Get documents where `bones` somewhere in `likes` array. +``` +/pets/*/[likes ni "bones"] +``` + +We can create more complicated filters +``` +( /[age <= 20] or /[lastName re "Do.*"] ) + and /pets/*/likes/[** in ["bones", "toys"]] +``` +Note about grouping parentheses and regular expression matching using `re` operator. + +`~` is a prefix matching operator (Since ejdb `v2.0.53`). +Prefix matching can benefit from using indexes. + +Get documents where `/lastName` starts with `"Do"`. +``` +/[lastName ~ Do] +``` + +### Arrays and maps can be matched as is + +Filter documents with `likes` array exactly matched to `["bones","jumping","toys"]` +``` +/**/[likes = ["bones","jumping","toys"]] +``` +Matching algorithms for arrays and maps are different: + +* Array elements are matched from start to end. In equal arrays + all values at the same index should be equal. +* Object maps matching consists of the following steps: + * Lexicographically sort object keys in both maps. + * Do matching keys and its values starting from the lowest key. + * If all corresponding keys and values in one map are fully matched to ones in other + and vice versa, maps considered to be equal. + For example: `{"f":"d","e":"j"}` and `{"e":"j","f":"d"}` are equal maps. + +### Conditions on key names + +Find JSON document having `firstName` key at root level. +``` +/[* = "firstName"] +``` +I this context `*` denotes a key name. + +You can use conditions on key name and key value at the same time: +``` +/[[* = "firstName"] = John] +``` + +Key name can be either `firstName` or `lastName` but should have `John` value in any case. +``` +/[[* in ["firstName", "lastName"]] = John] +``` + +It may be useful in queries with dynamic placeholders (C API): +``` +/[[* = :keyName] = :keyValue] +``` + +## JQL data modification + +`APPLY` section responsible for modification of documents content. + +``` +APPLY = ({'apply' | `upsert`} { PLACEHOLDER | json_object | json_array }) | 'del' +``` + +JSON patch specs conformed to `rfc7386` or `rfc6902` specifications followed after `apply` keyword. + +Let's add `address` object to all matched document +``` +/[firstName = John] | apply {"address":{"city":"New York", "street":""}} +``` + +If JSON object is an argument of `apply` section it will be treated as merge match (`rfc7386`) otherwise +it should be array which denotes `rfc6902` JSON patch. Placeholders also supported by `apply` section. +``` +/* | apply :? +``` + +Set the street name in `address` +``` +/[firstName = John] | apply [{"op":"replace", "path":"/address/street", "value":"Fifth Avenue"}] +``` + +Add `Neo` fish to the set of John's `pets` +``` +/[firstName = John] +| apply [{"op":"add", "path":"/pets/-", "value": {"name":"Neo", "kind":"fish"}}] +``` + +`upsert` updates existing document by given json argument used as merge patch + or inserts provided json argument as new document instance. + +``` +/[firstName = John] | upsert {"firstName": "John", "address":{"city":"New York"}} +``` + +### Non standard JSON patch extensions + +#### increment + +Increments numeric value identified by JSON path by specified value. + +Example: +``` + Document: {"foo": 1} + Patch: [{"op": "increment", "path": "/foo", "value": 2}] + Result: {"foo": 3} +``` +#### add_create + +Same as JSON patch `add` but creates intermediate object nodes for missing JSON path segments. + +Example: +``` +Document: {"foo": {"bar": 1}} +Patch: [{"op": "add_create", "path": "/foo/zaz/gaz", "value": 22}] +Result: {"foo":{"bar":1,"zaz":{"gaz":22}}} +``` + +Example: +``` +Document: {"foo": {"bar": 1}} +Patch: [{"op": "add_create", "path": "/foo/bar/gaz", "value": 22}] +Result: Error since element pointed by /foo/bar is not an object +``` + +#### swap + +Swaps two values of JSON document starting from `from` path. + +Swapping rules + +1. If value pointed by `from` not exists error will be raised. +1. If value pointed by `path` not exists it will be set by value from `from` path, + then object pointed by `from` path will be removed. +1. If both values pointed by `from` and `path` are presented they will be swapped. + +Example: + +``` +Document: {"foo": ["bar"], "baz": {"gaz": 11}} +Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/gaz"}] +Result: {"foo": [11], "baz": {"gaz": "bar"}} +``` + +Example (Demo of rule 2): + +``` +Document: {"foo": ["bar"], "baz": {"gaz": 11}} +Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/zaz"}] +Result: {"foo":[],"baz":{"gaz":11,"zaz":"bar"}} +``` + +### Removing documents + +Use `del` keyword to remove matched elements from collection: +``` +/FILTERS | del +``` + +Example: +``` +> k add family {"firstName":"Jack"} +< k 2 +> k query family /[firstName re "Ja.*"] +< k 2 {"firstName":"Jack"} + +# Remove selected elements from collection +> k query family /[firstName=Jack] | del +< k 2 {"firstName":"Jack"} +``` + +## JQL projections + +``` +PROJECTIONS = PROJECTION [ {'+' | '-'} PROJECTION ] + + PROJECTION = 'all' | json_path | join_clause +``` + +Projection allows to get only subset of JSON document excluding not needed data. + +Lets add one more document to our collection: + +```sh +$ cat << EOF | curl -d @- -H'X-Access-Token:myaccess01' -X POST http://localhost:9191/family +{ +"firstName":"Jack", +"lastName":"Parker", +"age":35, +"pets":[{"name":"Sonic", "kind":"mouse", "likes":[]}] +} +EOF +``` +Now query only pet owners firstName and lastName from collection. + +``` +> k query family /* | /{firstName,lastName} + +< k 3 {"firstName":"Jack","lastName":"Parker"} +< k 1 {"firstName":"John","lastName":"Doe"} +< k +``` + +Add `pets` array for every document +``` +> k query family /* | /{firstName,lastName} + /pets + +< k 3 {"firstName":"Jack","lastName":"Parker","pets":[... +< k 1 {"firstName":"John","lastName":"Doe","pets":[... +``` + +Exclude only `pets` field from documents +``` +> k query family /* | all - /pets + +< k 3 {"firstName":"Jack","lastName":"Parker","age":35} +< k 1 {"firstName":"John","lastName":"Doe","age":28,"address":{"city":"New York","street":"Fifth Avenue"}} +< k +``` +Here `all` keyword used denoting whole document. + +Get `age` and the first pet in `pets` array. +``` +> k query family /[age > 20] | /age + /pets/0 + +< k 3 {"age":35,"pets":[{"name":"Sonic","kind":"mouse","likes":[]}]} +< k 1 {"age":28,"pets":[{"name":"Rexy rex","kind":"dog","likes":["bones","jumping","toys"]}]} +< k +``` + +## JQL collection joins + +Join materializes reference to document to a real document objects which will replace reference inplace. + +Documents are joined by their primary keys only. + +Reference keys should be stored in referrer document as number or string field. + +Joins can be specified as part of projection expression +in the following form: + +``` +/.../field k add artists {"name":"Leonardo Da Vinci", "years":[1452,1519]} +< k 1 +> k add paintings {"name":"Mona Lisa", "year":1490, "origin":"Italy", "artist": 1} +< k 1 +> k add paintings {"name":"Madonna Litta - Madonna And The Child", "year":1490, "origin":"Italy", "artist": 1} +< k 2 + +# Lists paintings documents + +> k @paintings/* +< k 2 {"name":"Madonna Litta - Madonna And The Child","year":1490,"origin":"Italy","artist":1} +< k 1 {"name":"Mona Lisa","year":1490,"origin":"Italy","artist":1} +< k +> + +# Do simple join with artists collection + +> k @paintings/* | /artist k @paintings/* | /artist + +# Same results as above: + +> k @paintings/* | /{name, artist k add paintings {"name":"Mona Lisa2", "year":1490, "origin":"Italy", "artist": 9999} +< k 3 +> k @paintings/* | /artist k add family {"firstName":"John", "lastName":"Ryan", "age":39} +< k 4 +``` + +``` +> k query family /* | /{firstName,lastName,age} | asc /firstName desc /age +< k 3 {"firstName":"Jack","lastName":"Parker","age":35} +< k 4 {"firstName":"John","lastName":"Ryan","age":39} +< k 1 {"firstName":"John","lastName":"Doe","age":28} +< k +``` + +`asc, desc` instructions may use indexes defined for collection to avoid a separate documents sorting stage. + +## JQL Options + +``` +OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }... +``` + +* `skip n` Skip first `n` records before first element in result set +* `limit n` Set max number of documents in result set +* `count` Returns only `count` of matched documents + ``` + > k query family /* | count + < k 3 + < k + ``` +* `noidx` Do not use any indexes for query execution. +* `inverse` By default query scans documents from most recently added to older ones. + This option inverts scan direction to opposite and activates `noidx` mode. + Has no effect if query has `asc/desc` sorting clauses. + +## JQL Indexes and performance tips + +Database index can be build for any JSON field path containing values of number or string type. +Index can be an `unique` ‐ not allowing value duplication and `non unique`. +The following index mode bit mask flags are used (defined in `ejdb2.h`): + +Index mode | Description +--- | --- +0x01 EJDB_IDX_UNIQUE | Index is unique +0x04 EJDB_IDX_STR | Index for JSON `string` field value type +0x08 EJDB_IDX_I64 | Index for `8 bytes width` signed integer field values +0x10 EJDB_IDX_F64 | Index for `8 bytes width` signed floating point field values. + +For example unique index of string type will be specified by `EJDB_IDX_UNIQUE | EJDB_IDX_STR` = `0x05`. +Index can be defined for only one value type located under specific path in json document. + +Lets define non unique string index for `/lastName` path: +``` +> k idx family 4 /lastName +< k +``` +Index selection for queries based on set of heuristic rules. + +You can always check index usage by issuing `explain` command in WS API: +``` +> k explain family /[lastName=Doe] and /[age!=27] +< k explain [INDEX] MATCHED STR|3 /lastName EXPR1: 'lastName = Doe' INIT: IWKV_CURSOR_EQ +[INDEX] SELECTED STR|3 /lastName EXPR1: 'lastName = Doe' INIT: IWKV_CURSOR_EQ + [COLLECTOR] PLAIN +``` + +The following statements are taken into account when using EJDB2 indexes: +* Only one index can be used for particular query execution +* If query consist of `or` joined part at top level or contains `negated` expressions at the top level + of query expression - indexes will not be in use at all. + So no indexes below: + ``` + /[lastName != Andy] + + /[lastName = "John"] or /[lastName = Peter] + + ``` + But will be used `/lastName` index defined above + ``` + /[lastName = Doe] + + /[lastName = Doe] and /[age = 28] + + /[lastName = Doe] and not /[age = 28] + + /[lastName = Doe] and /[age != 28] + ``` +* The following operators are supported by indexes (ejdb 2.0.x): + * `eq, =` + * `gt, >` + * `gte, >=` + * `lt, <` + * `lte, <=` + * `in` + * `~` (Prefix matching since ejdb 2.0.53) + +* `ORDERBY` clauses may use indexes to avoid result set sorting. +* Array fields can also be indexed. Let's outline typical use case: indexing of some entity tags: + ``` + > k add books {"name":"Mastering Ultra", "tags":["ultra", "language", "bestseller"]} + < k 1 + > k add books {"name":"Learn something in 24 hours", "tags":["bestseller"]} + < k 2 + > k query books /* + < k 2 {"name":"Learn something in 24 hours","tags":["bestseller"]} + < k 1 {"name":"Mastering Ultra","tags":["ultra","language","bestseller"]} + < k + ``` + Create string index for `/tags` + ``` + > k idx books 4 /tags + < k + ``` + Filter books by `bestseller` tag and show index usage in query: + ``` + > k explain books /tags/[** in ["bestseller"]] + < k explain [INDEX] MATCHED STR|4 /tags EXPR1: '** in ["bestseller"]' INIT: IWKV_CURSOR_EQ + [INDEX] SELECTED STR|4 /tags EXPR1: '** in ["bestseller"]' INIT: IWKV_CURSOR_EQ + [COLLECTOR] PLAIN + + < k 1 {"name":"Mastering Ultra","tags":["ultra","language","bestseller"]} + < k 2 {"name":"Learn something in 24 hours","tags":["bestseller"]} + < k + ``` + +### Performance tip: Physical ordering of documents + +All documents in collection are sorted by their primary key in `descending` order. +So if you use auto generated keys (`ejdb_put_new`) you may be sure what documents fetched as result of +full scan query will be ordered according to the time of insertion in descendant order, +unless you don't use query sorting, indexes or `inverse` keyword. + +### Performance tip: Brute force scan vs indexed access + +In many cases, using index may drop down the overall query performance. +Because index collection contains only document references (`id`) and engine may perform +an addition document fetching by its primary key to finish query matching. +So for not so large collections a brute scan may perform better than scan using indexes. +However, exact matching operations: `eq`, `in` and `sorting` by natural index order +will benefit from index in most cases. + + +### Performance tip: Get rid of unnecessary document data + +If you'd like update some set of documents with `apply` or `del` operations +but don't want fetching all of them as result of query - just add `count` +modifier to the query to get rid of unnecessary data transferring and json data conversion. + + + +# HTTP REST/Websocket API endpoint + +EJDB engine provides the ability to start a separate HTTP/Websocket endpoint worker exposing network API for quering and data modifications. + +The easiest way to expose database over the network is using the standalone `jbs` server. (Of course if you plan to avoid `C API` integration). + +## jbs server + +``` +jbs -h + +EJDB 2.0.0 standalone REST/Websocket server. http://ejdb.org + + --file <> Database file path. Default: db.jb + -f <> (same as --file) + --port ## HTTP port number listen to. Default: 9191 + -p ## (same as --port) + --bind <> Address server listen. Default: localhost + -b <> (same as --bind) + --access <> Server access token matched to 'X-Access-Token' HTTP header value + -a <> (same as --access) + --trunc Cleanup existing database file on open + -t (same as --trunc) + --wal Use write ahead logging (WAL). Must be set for data durability. + -w (same as --wal) + +Advanced options + --sbz ## Max sorting buffer size. If exceeded, an overflow temp file for data will be created. Default: 16777216, min: 1048576 + --dsz ## Initial size of buffer to process/store document on queries. Preferable average size of document. Default: 65536, min: 16384 + --bsz ## Max HTTP/WS API document body size. Default: 67108864, min: 524288 + +Use any of the following input formats: + -arg -arg= -arg + +Use the -h, -help or -? to get this information again. +``` + +## HTTP API + +Access to HTTP endpoint can be protected by a token specified with `--access` +command flag or by C API `EJDB_HTTP` options. If access token specified on server, client must provide `X-Access-Token` HTTP header value. If token is required and not provided by client the `401` HTTP code will be reported. If access token is not matched to the token provided the `403` HTTP code will be returned. +For any other errors server will respond with `500` error code. + +## REST API + +### POST /{collection} +Add a new document to the `collection`. +* `200` success. Body: a new document identifier as `int64` number + +### PUT /{collection}/{id} +Replaces/store document under specific numeric `id` +* `200` on success. Empty body + +### DELETE /{collection}/{id} +Removes document identified by `id` from a `collection` +* `200` on success. Empty body +* `404` if document not found + +### PATCH /{collection}/{id} +Patch a document identified by `id` by [rfc7396](https://tools.ietf.org/html/rfc7396), +[rfc6902](https://tools.ietf.org/html/rfc6902) data. +* `200` on success. Empty body + +### GET | HEAD /{collections}/{id} +Retrieve document identified by `id` from a `collection`. +* `200` on success. Body: JSON document text. + * `content-type:application/json` + * `content-length:` +* `404` if document not found + +### POST / +Query a collection by provided query as POST body. +Body of query should contains collection name in use in the first filter element: `@collection_name/...` +Request headers: +* `X-Hints` comma separated extra hints to ejdb2 database engine. + * `explain` Show query execution plan before first element in result set separated by `--------------------` line. +Response: +* Response data transfered using [HTTP chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) +* `200` on success. +* JSON documents separated by `\n` in the following format: + ``` + \r\n\t + ... + ``` + +Example: + +``` +curl -v --data-raw '@family/[age > 18]' -H 'X-Access-Token:myaccess01' http://localhost:9191 +* Rebuilt URL to: http://localhost:9191/ +* Trying 127.0.0.1... +* TCP_NODELAY set +* Connected to localhost (127.0.0.1) port 9191 (#0) +> POST / HTTP/1.1 +> Host: localhost:9191 +> User-Agent: curl/7.58.0 +> Accept: */* +> X-Access-Token:myaccess01 +> Content-Length: 18 +> Content-Type: application/x-www-form-urlencoded +> +* upload completely sent off: 18 out of 18 bytes +< HTTP/1.1 200 OK +< connection:keep-alive +< content-type:application/json +< transfer-encoding:chunked +< + +4 {"firstName":"John","lastName":"Ryan","age":39} +3 {"firstName":"Jack","lastName":"Parker","age":35,"pets":[{"name":"Sonic","kind":"mouse","likes":[]}]} +1 {"firstName":"John","lastName":"Doe","age":28,"pets":[{"name":"Rexy rex","kind":"dog","likes":["bones","jumping","toys"]},{"name":"Grenny","kind":"parrot","likes":["green color","night","toys"]}],"address":{"city":"New York","street":"Fifth Avenue"}} +* Connection #0 to host localhost left intact +``` + +``` +curl --data-raw '@family/[lastName = "Ryan"]' -H 'X-Access-Token:myaccess01' -H 'X-Hints:explain' http://localhost:9191 +[INDEX] MATCHED STR|3 /lastName EXPR1: 'lastName = "Ryan"' INIT: IWKV_CURSOR_EQ +[INDEX] SELECTED STR|3 /lastName EXPR1: 'lastName = "Ryan"' INIT: IWKV_CURSOR_EQ + [COLLECTOR] PLAIN +-------------------- +4 {"firstName":"John","lastName":"Ryan","age":39} +``` + +### OPTIONS / +Fetch ejdb JSON metadata and available HTTP methods in `Allow` response header. +Example: +``` +curl -X OPTIONS -H 'X-Access-Token:myaccess01' http://localhost:9191/ +{ + "version": "2.0.0", + "file": "db.jb", + "size": 16384, + "collections": [ + { + "name": "family", + "dbid": 3, + "rnum": 3, + "indexes": [ + { + "ptr": "/lastName", + "mode": 4, + "idbf": 64, + "dbid": 4, + "rnum": 3 + } + ] + } + ] +} +``` + +## Websocket API + +EJDB supports simple text based protocol over HTTP websocket protocol. +You can use interactive websocket CLI tool [wscat](https://www.npmjs.com/package/@softmotions/wscat) to communicate with server by hands. + +### Commands + +#### ? +Will respond with the following help text message: +``` +wscat -H 'X-Access-Token:myaccess01' -c http://localhost:9191 +> ? +< + info + get + set + add + del + patch + idx + rmi + rmc + query + explain + +> +``` + +Note about `` prefix before every command; It is an arbitrary key chosen by client and designated to identify particular websocket request, this key will be returned with response to request and allows client to identify that response for his particular request. + +Errors are returned in the following format: +``` + ERROR: +``` + +#### ` info` +Get database metadatas as JSON document. + +#### ` get ` +Retrieve document identified by `id` from a `collection`. +If document is not found `IWKV_ERROR_NOTFOUND` will be returned. + +Example: +``` +> k get family 3 +< k 3 { + "firstName": "Jack", + "lastName": "Parker", + "age": 35, + "pets": [ + { + "name": "Sonic", + "kind": "mouse", + "likes": [] + } + ] +} +``` +If document not found we will get error: +``` +> k get family 55 +< k ERROR: Key not found. (IWKV_ERROR_NOTFOUND) +> +``` + +#### ` set ` +Replaces/add document under specific numeric `id`. +`Collection` will be created automatically if not exists. + +#### ` add ` +Add new document to `` New `id` of document will be generated +and returned as response. `Collection> will be created automatically if not exists. + +Example: +``` +> k add mycollection {"foo":"bar"} +< k 1 +> k add mycollection {"foo":"bar"} +< k 2 +> +``` + +#### ` del ` +Remove document identified by `id` from the `collection`. +If document is not found `IWKV_ERROR_NOTFOUND` will be returned. + +#### ` patch ` +Apply [rfc7396](https://tools.ietf.org/html/rfc7396) or +[rfc6902](https://tools.ietf.org/html/rfc6902) patch to the document identified by `id`. +If document is not found `IWKV_ERROR_NOTFOUND` will be returned. + +#### ` query ` +Execute query on documents in specified `collection`. +**Response:** A set of WS messages with document boidies terminated by the last +message with empty body. +``` +> k query family /* | /firstName +< k 4 {"firstName":"John"} +< k 3 {"firstName":"Jack"} +< k 1 {"firstName":"John"} +< k +``` +Note about last message: `` with no body. + +#### ` explain ` +Same as ` query ` but the first response message will +be prefixed by ` explain` and contains query execution plan. + +Example: +``` +> k explain family /* | /firstName +< k explain [INDEX] NO [COLLECTOR] PLAIN + +< k 4 {"firstName":"John"} +< k 3 {"firstName":"Jack"} +< k 1 {"firstName":"John"} +< k +``` + +#### +Execute query text. Body of query should contains collection name in use in the first filter element: `@collection_name/...`. Behavior is the same as for: ` query ` + +#### ` idx ` +Ensure index with specified `mode` (bitmask flag) for given json `path` and `collection`. +Collection will be created if not exists. + +Index mode | Description +--- | --- +0x01 EJDB_IDX_UNIQUE | Index is unique +0x04 EJDB_IDX_STR | Index for JSON `string` field value type +0x08 EJDB_IDX_I64 | Index for `8 bytes width` signed integer field values +0x10 EJDB_IDX_F64 | Index for `8 bytes width` signed floating point field values. + +##### Example +Set unique string index `(0x01 & 0x04) = 5` on `/name` JSON field: +``` +k idx mycollection 5 /name +``` + +#### ` rmi ` +Remove index with specified `mode` (bitmask flag) for given json `path` and `collection`. +Return error if given index not found. + +#### ` rmc ` +Remove collection and all of its data. +Note: If `collection` is not found no errors will be reported. + + + + +# Docker support + +If you have [Docker]("https://www.docker.com/") installed, you can build a Docker image and run it in a container + +``` +cd docker +docker build -t ejdb2 . +docker run -d -p 9191:9191 --name myEJDB ejdb2 --access myAccessKey +``` + +or get an image of `ejdb2` directly from the Docker Hub + +``` +docker run -d -p 9191:9191 --name myEJDB softmotions/ejdb2 --access myAccessKey +``` + + +# C API + +EJDB can be embedded into any `C/C++` application. +`C API` documented in the following headers: + +* [ejdb.h](https://github.com/Softmotions/ejdb/blob/master/src/ejdb2.h) Main API functions +* [jbl.h](https://github.com/Softmotions/ejdb/blob/master/src/jbl/jbl.h) JSON documents management API +* [jql.h](https://github.com/Softmotions/ejdb/blob/master/src/jql/jql.h) Query building API + +Example application: +```c +#include + +#define CHECK(rc_) \ + if (rc_) { \ + iwlog_ecode_error3(rc_); \ + return 1; \ + } + +static iwrc documents_visitor(EJDB_EXEC *ctx, const EJDB_DOC doc, int64_t *step) { + // Print document to stderr + return jbl_as_json(doc->raw, jbl_fstream_json_printer, stderr, JBL_PRINT_PRETTY); +} + +int main() { + + EJDB_OPTS opts = { + .kv = { + .path = "example.db", + .oflags = IWKV_TRUNC + } + }; + EJDB db; // EJDB2 storage handle + int64_t id; // Document id placeholder + JQL q = 0; // Query instance + JBL jbl = 0; // Json document + + iwrc rc = ejdb_init(); + CHECK(rc); + + rc = ejdb_open(&opts, &db); + CHECK(rc); + + // First record + rc = jbl_from_json(&jbl, "{\"name\":\"Bianca\", \"age\":4}"); + RCGO(rc, finish); + rc = ejdb_put_new(db, "parrots", jbl, &id); + RCGO(rc, finish); + jbl_destroy(&jbl); + + // Second record + rc = jbl_from_json(&jbl, "{\"name\":\"Darko\", \"age\":8}"); + RCGO(rc, finish); + rc = ejdb_put_new(db, "parrots", jbl, &id); + RCGO(rc, finish); + jbl_destroy(&jbl); + + // Now execute a query + rc = jql_create(&q, "parrots", "/[age > :age]"); + RCGO(rc, finish); + + EJDB_EXEC ux = { + .db = db, + .q = q, + .visitor = documents_visitor + }; + + // Set query placeholder value. + // Actual query will be /[age > 3] + rc = jql_set_i64(q, "age", 0, 3); + RCGO(rc, finish); + + // Now execute the query + rc = ejdb_exec(&ux); + +finish: + jql_destroy(&q); + jbl_destroy(&jbl); + ejdb_close(&db); + CHECK(rc); + return 0; +} +``` + +Compile and run: +``` +gcc -std=gnu11 -Wall -pedantic -c -o example1.o example1.c +gcc -o example1 example1.o -lejdb2 + +./example1 +{ + "name": "Darko", + "age": 8 +}{ + "name": "Bianca", + "age": 4 +} +``` + +# License +``` + +MIT License + +Copyright (c) 2012-2021 Softmotions Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/WINDOWS.md b/WINDOWS.md new file mode 100644 index 0000000..9e5ffb5 --- /dev/null +++ b/WINDOWS.md @@ -0,0 +1,50 @@ +# Windows cross compilation + +**Note:** HTTP/Websocket network API is disabled and not supported +on Windows until porting of http://facil.io library + +Nodejs/Dart bindings not yet ported to Windows. + +## Prerequisites + +```sh +sudo apt-get install bison flex libtool ruby scons intltool libtool-bin p7zip-full wine +``` + +## Build + +### Prepare MXE + +```sh +git submodule update --init +``` + +`nano ./mxe/settings.mk`: + +``` +JOBS := 1 +MXE_TARGETS := x86_64-w64-mingw32.static +LOCAL_PKG_LIST := cunit libiberty +.DEFAULT local-pkg-list: +local-pkg-list: $(LOCAL_PKG_LIST) +``` + +``` +cd ./mxe +make +``` + +### Cross compilation + +```bash +mkdir ./build +cd ./build + +export MXE_HOME=/mxe + +cmake .. -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=../win64-tc.cmake \ + -DENABLE_HTTP=OFF + +make package +``` \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..903f3fc --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,3 @@ +analyzer: + exclude: + - build/** \ No newline at end of file diff --git a/cmake/Modules/AddFacil.cmake b/cmake/Modules/AddFacil.cmake new file mode 100644 index 0000000..a6d3af6 --- /dev/null +++ b/cmake/Modules/AddFacil.cmake @@ -0,0 +1,86 @@ +include(ExternalProject) + +if (${CMAKE_VERSION} VERSION_LESS "3.8.0") + set(_UPDATE_DISCONNECTED 0) +else() + set(_UPDATE_DISCONNECTED 1) +endif() + +set(FACIL_SOURCE_DIR "${CMAKE_BINARY_DIR}/src/extern_facil") +set(FACIL_BINARY_DIR "${CMAKE_BINARY_DIR}/src/extern_facil") +set(FACIL_INCLUDE_DIR "${FACIL_BINARY_DIR}/lib/facil") +set(FACIL_LIBRARY_DIR "${FACIL_BINARY_DIR}") + +if("${FACIL_URL}" STREQUAL "") + if(EXISTS ${CMAKE_SOURCE_DIR}/facil.zip) + set(FACIL_URL ${CMAKE_SOURCE_DIR}/facil.zip) + else() + set(FACIL_URL https://github.com/Softmotions/facil.io/archive/master.zip) + endif() +endif() + +message("FACIL_URL: ${FACIL_URL}") + +set(CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_C_FLAGS=-fPIC -fvisibility=hidden) + +foreach(extra + CMAKE_C_COMPILER + CMAKE_TOOLCHAIN_FILE + ANDROID_PLATFORM + ANDROID_ABI + TEST_TOOL_CMD + PLATFORM + ENABLE_BITCODE + ENABLE_ARC + ENABLE_VISIBILITY + ENABLE_STRICT_TRY_COMPILE + ARCHS) + if(DEFINED ${extra}) + list(APPEND CMAKE_ARGS "-D${extra}=${${extra}}") + endif() +endforeach() +message("FACIL CMAKE_ARGS: ${CMAKE_ARGS}") + +ExternalProject_Add( + extern_facil + URL ${FACIL_URL} + DOWNLOAD_NAME facil.zip + TIMEOUT 360 + # Remove in-source makefile to avoid clashing + PATCH_COMMAND rm -f ./makefile + PREFIX ${CMAKE_BINARY_DIR} + BUILD_IN_SOURCE ON + GIT_PROGRESS ON + UPDATE_COMMAND "" + INSTALL_COMMAND "" + UPDATE_DISCONNECTED ${_UPDATE_DISCONNECTED} + LOG_DOWNLOAD OFF + LOG_UPDATE OFF + LOG_BUILD OFF + LOG_CONFIGURE OFF + LOG_INSTALL OFF + CMAKE_ARGS ${CMAKE_ARGS} + BUILD_BYPRODUCTS "${FACIL_LIBRARY_DIR}/libfacil.io.a" +) + +add_library(facil_s STATIC IMPORTED GLOBAL) +set_target_properties( + facil_s + PROPERTIES + IMPORTED_LOCATION "${FACIL_LIBRARY_DIR}/libfacil.io.a" +) +add_dependencies(facil_s extern_facil) + +if (DO_INSTALL_CORE) + install(FILES "${FACIL_LIBRARY_DIR}/libfacil.io.a" + RENAME "libfacilio-1.a" + DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endif() + +list(APPEND PROJECT_LLIBRARIES facil_s) +list(APPEND PROJECT_INCLUDE_DIRS "${FACIL_INCLUDE_DIR}" + "${FACIL_INCLUDE_DIR}/fiobj" + "${FACIL_INCLUDE_DIR}/http" + "${FACIL_INCLUDE_DIR}/cli" + "${FACIL_INCLUDE_DIR}/tls") diff --git a/cmake/Modules/AddIOWOW.cmake b/cmake/Modules/AddIOWOW.cmake new file mode 100644 index 0000000..854bbaf --- /dev/null +++ b/cmake/Modules/AddIOWOW.cmake @@ -0,0 +1,84 @@ +include(ExternalProject) + +if (${CMAKE_VERSION} VERSION_LESS "3.8.0") + set(_UPDATE_DISCONNECTED 0) +else() + set(_UPDATE_DISCONNECTED 1) +endif() + +set(IOWOW_INCLUDE_DIR "${CMAKE_BINARY_DIR}/include") + +if("${IOWOW_URL}" STREQUAL "") + if(EXISTS ${CMAKE_SOURCE_DIR}/iowow.zip) + set(IOWOW_URL ${CMAKE_SOURCE_DIR}/iowow.zip) + else() + set(IOWOW_URL https://github.com/Softmotions/iowow/archive/master.zip) + endif() +endif() + +message("IOWOW_URL: ${IOWOW_URL}") + +if (IOS) + set(BYPRODUCT "${CMAKE_BINARY_DIR}/lib/libiowow-1.a") +else() + set(BYPRODUCT "${CMAKE_BINARY_DIR}/src/extern_iowow-build/src/libiowow-1.a") +endif() + +set(CMAKE_ARGS -DOWNER_PROJECT_NAME=${PROJECT_NAME} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR} + -DASAN=${ASAN} + -DBUILD_SHARED_LIBS=OFF + -DBUILD_EXAMPLES=OFF) + +foreach(extra + CMAKE_C_COMPILER + CMAKE_TOOLCHAIN_FILE + ANDROID_PLATFORM + ANDROID_ABI + TEST_TOOL_CMD + PLATFORM + ENABLE_BITCODE + ENABLE_ARC + ENABLE_VISIBILITY + ENABLE_STRICT_TRY_COMPILE + ARCHS) + if(DEFINED ${extra}) + list(APPEND CMAKE_ARGS "-D${extra}=${${extra}}") + endif() +endforeach() +message("IOWOW CMAKE_ARGS: ${CMAKE_ARGS}") + +ExternalProject_Add( + extern_iowow + URL ${IOWOW_URL} + DOWNLOAD_NAME iowow.zip + TIMEOUT 360 + PREFIX ${CMAKE_BINARY_DIR} + BUILD_IN_SOURCE OFF + UPDATE_COMMAND "" + UPDATE_DISCONNECTED ${_UPDATE_DISCONNECTED} + LOG_DOWNLOAD OFF + LOG_UPDATE OFF + LOG_BUILD OFF + LOG_CONFIGURE OFF + LOG_INSTALL OFF + CMAKE_ARGS ${CMAKE_ARGS} + BUILD_BYPRODUCTS ${BYPRODUCT} +) + +add_library(iowow_s STATIC IMPORTED GLOBAL) +set_target_properties( + iowow_s + PROPERTIES + IMPORTED_LOCATION ${BYPRODUCT} +) +add_dependencies(iowow_s extern_iowow) + +if (DO_INSTALL_CORE) + install(FILES "${BYPRODUCT}" + DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endif() + +list(APPEND PROJECT_LLIBRARIES iowow_s m) +list(APPEND PROJECT_INCLUDE_DIRS "${IOWOW_INCLUDE_DIR}") diff --git a/cmake/Modules/CMakeFindJavaCommon.cmake b/cmake/Modules/CMakeFindJavaCommon.cmake new file mode 100644 index 0000000..46b6280 --- /dev/null +++ b/cmake/Modules/CMakeFindJavaCommon.cmake @@ -0,0 +1,31 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + + +# Do not include this module directly from code outside CMake! +set(_JAVA_HOME "") +if(JAVA_HOME AND IS_DIRECTORY "${JAVA_HOME}") + set(_JAVA_HOME "${JAVA_HOME}") + set(_JAVA_HOME_EXPLICIT 1) +else() + set(_ENV_JAVA_HOME "") + if(DEFINED ENV{JAVA_HOME}) + file(TO_CMAKE_PATH "$ENV{JAVA_HOME}" _ENV_JAVA_HOME) + endif() + if(_ENV_JAVA_HOME AND IS_DIRECTORY "${_ENV_JAVA_HOME}") + set(_JAVA_HOME "${_ENV_JAVA_HOME}") + set(_JAVA_HOME_EXPLICIT 1) + else() + set(_CMD_JAVA_HOME "") + if(APPLE AND EXISTS /usr/libexec/java_home) + execute_process(COMMAND /usr/libexec/java_home + OUTPUT_VARIABLE _CMD_JAVA_HOME OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() + if(_CMD_JAVA_HOME AND IS_DIRECTORY "${_CMD_JAVA_HOME}") + set(_JAVA_HOME "${_CMD_JAVA_HOME}") + set(_JAVA_HOME_EXPLICIT 0) + endif() + unset(_CMD_JAVA_HOME) + endif() + unset(_ENV_JAVA_HOME) +endif() diff --git a/cmake/Modules/DebChangelog.cmake b/cmake/Modules/DebChangelog.cmake new file mode 100644 index 0000000..706d1b7 --- /dev/null +++ b/cmake/Modules/DebChangelog.cmake @@ -0,0 +1,54 @@ +## -*- mode:cmake; coding:utf-8; -*- +# Set output: +# CHANGELOG_LAST_LINE +# CHANGELOG_LAST_VERSION - Last changelog version +# CHANGELOG_LAST_VERSION_MAJOR - Last changelog major version +# CHANGELOG_LAST_VERSION_MINOR - Last changelog minor version +# CHANGELOG_LAST_VERSION_PATCH - Last changelog patch version +# CHANGELOG_LAST_MESSAGE - Last changelog description + +if (NOT DEB_CHANGELOG) + set(DEB_CHANGELOG "${CMAKE_SOURCE_DIR}/Changelog") +endif() + +if (NOT EXISTS ${DEB_CHANGELOG}) + if (DEB_CHANGELOG_REQUIRED) + message(FATAL_ERROR "Missing required project changelog file: ${DEB_CHANGELOG}") + else() + message("Missing project changelog file: ${DEB_CHANGELOG}") + return() + endif() +endif() +message(STATUS "Using project changelog file: ${DEB_CHANGELOG}") + +file(STRINGS "${DEB_CHANGELOG}" _DEBCHANGELOGN) + +foreach(CLINE IN LISTS _DEBCHANGELOGN) + if (NOT CHANGELOG_LAST_VERSION) + #ejdb (1.2.6) testing; urgency=low + string(REGEX MATCH "^[A-Za-z0-9_].*[ \t]+\\((([0-9]+)\\.([0-9]+)\\.([0-9]+))\\)[ \t]+[A-Za-z]+;[ \t].*" _MATCHED "${CLINE}") + if (_MATCHED) + set(CHANGELOG_LAST_LINE "${_MATCHED}") + set(CHANGELOG_LAST_VERSION "${CMAKE_MATCH_1}") + set(CHANGELOG_LAST_VERSION_MAJOR "${CMAKE_MATCH_2}") + set(CHANGELOG_LAST_VERSION_MINOR "${CMAKE_MATCH_3}") + set(CHANGELOG_LAST_VERSION_PATCH "${CMAKE_MATCH_4}") + endif() + elseif(NOT CHANGELOG_LAST_MESSAGE) + string(REGEX MATCH "^[A-Za-z0-9_].*[ \t]+\\((([0-9]+)\\.([0-9]+)\\.([0-9]+))\\)[ \t]+[A-Za-z]+;[ \t].*" _MATCHED "${CLINE}") + if (_MATCHED) + string(STRIP "${_CDESC}" CHANGELOG_LAST_MESSAGE) + return() + endif() + if (CLINE) + string(REGEX MATCH "^[ \t]*\\-\\-[ \t]+" _MATCHED "${CLINE}") + if (_MATCHED) + string(STRIP "${_CDESC}" CHANGELOG_LAST_MESSAGE) + return() + endif() + set(_CDESC "${_CDESC}\n${CLINE}") + endif() + endif() +endforeach(CLINE) + +message(FATAL_ERROR "Invalid changelog file: ${DEB_CHANGELOG}") diff --git a/cmake/Modules/FindCUnit.cmake b/cmake/Modules/FindCUnit.cmake new file mode 100644 index 0000000..a0cf0e8 --- /dev/null +++ b/cmake/Modules/FindCUnit.cmake @@ -0,0 +1,24 @@ +# Find the CUnit headers and libraries +# +# CUNIT_INCLUDE_DIRS - The CUnit include directory (directory where CUnit/CUnit.h was found) +# CUNIT_LIBRARIES - The libraries needed to use CUnit +# CUNIT_FOUND - True if CUnit found in system + + +FIND_PATH(CUNIT_INCLUDE_DIR NAMES CUnit/CUnit.h) +MARK_AS_ADVANCED(CUNIT_INCLUDE_DIR) + +FIND_LIBRARY(CUNIT_LIBRARY NAMES + cunit + libcunit + cunitlib +) +MARK_AS_ADVANCED(CUNIT_LIBRARY) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(CUnit DEFAULT_MSG CUNIT_LIBRARY CUNIT_INCLUDE_DIR) + +IF(CUNIT_FOUND) + SET(CUNIT_LIBRARIES ${CUNIT_LIBRARY}) + SET(CUNIT_INCLUDE_DIRS ${CUNIT_INCLUDE_DIR}) +ENDIF(CUNIT_FOUND) diff --git a/cmake/Modules/FindIOWOW.cmake b/cmake/Modules/FindIOWOW.cmake new file mode 100644 index 0000000..d93dfd0 --- /dev/null +++ b/cmake/Modules/FindIOWOW.cmake @@ -0,0 +1,39 @@ +# Find the IOWOW headers and libraries, attemting to import IOWOW exported targets, +# +# IOWOW_INCLUDE_DIRS - The IOWOW include directory +# IOWOW_LIBRARIES - The libraries needed to use IOWOW +# IOWOW_FOUND - True if IOWOW found in system + +find_path(IOWOW_INCLUDE_DIR NAMES iowow/iowow.h) +mark_as_advanced(IOWOW_INCLUDE_DIR) +find_library(IOWOW_LIBRARY NAMES + iowow + libiowow + iowowlib +) +mark_as_advanced(IOWOW_LIBRARY) +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(IOWOW DEFAULT_MSG IOWOW_LIBRARY IOWOW_INCLUDE_DIR) + +if(IOWOW_FOUND) + set(IOWOW_LIBRARIES ${IOWOW_LIBRARY}) + set(IOWOW_INCLUDE_DIRS ${IOWOW_INCLUDE_DIR}) + find_path(IOWOW_EXPORTS_DIR + NAMES "iowow/iowow-exports.cmake" + PATHS /usr/share /usr/local/share) + mark_as_advanced(IOWOW_EXPORTS_DIR) + if(IOWOW_EXPORTS_DIR) + if(EXISTS ${IOWOW_EXPORTS_DIR}/iowow/iowow-exports.cmake) + include(${IOWOW_EXPORTS_DIR}/iowow/iowow-exports.cmake) + message("-- Imported ${IOWOW_EXPORTS_DIR}/iowow/iowow-exports.cmake") + endif() + if(EXISTS ${IOWOW_EXPORTS_DIR}/iowow/iowow-static-exports.cmake) + include(${IOWOW_EXPORTS_DIR}/iowow/iowow-static-exports.cmake) + message("-- Imported ${IOWOW_EXPORTS_DIR}/iowow/iowow-static-exports.cmake") + endif() + endif(IOWOW_EXPORTS_DIR) +elseif(IOWOW_FIND_REQUIRED) + message(FATAL_ERROR "Could not find libiowow.") +endif(IOWOW_FOUND) + + diff --git a/cmake/Modules/FindJNI.cmake b/cmake/Modules/FindJNI.cmake new file mode 100644 index 0000000..2a4bf7f --- /dev/null +++ b/cmake/Modules/FindJNI.cmake @@ -0,0 +1,406 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindJNI +------- + +Find Java Native Interface (JNI) libraries. + +JNI enables Java code running in a Java Virtual Machine (JVM) to call +and be called by native applications and libraries written in other +languages such as C, C++. + +This module finds if Java is installed and determines where the +include files and libraries are. It also determines what the name of +the library is. The caller may set variable ``JAVA_HOME`` to specify a +Java installation prefix explicitly. + +Result Variables +^^^^^^^^^^^^^^^^ + +This module sets the following result variables: + +``JNI_INCLUDE_DIRS`` + the include dirs to use +``JNI_LIBRARIES`` + the libraries to use (JAWT and JVM) +``JNI_FOUND`` + TRUE if JNI headers and libraries were found. + +Cache Variables +^^^^^^^^^^^^^^^ + +The following cache variables are also available to set or use: + +``JAVA_AWT_LIBRARY`` + the path to the Java AWT Native Interface (JAWT) library +``JAVA_JVM_LIBRARY`` + the path to the Java Virtual Machine (JVM) library +``JAVA_INCLUDE_PATH`` + the include path to jni.h +``JAVA_INCLUDE_PATH2`` + the include path to jni_md.h and jniport.h +#]=======================================================================] + +# Expand {libarch} occurrences to java_libarch subdirectory(-ies) and set ${_var} +macro(java_append_library_directories _var) + # Determine java arch-specific library subdir + # Mostly based on openjdk/jdk/make/common/shared/Platform.gmk as of openjdk + # 1.6.0_18 + icedtea patches. However, it would be much better to base the + # guess on the first part of the GNU config.guess platform triplet. + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") + if(CMAKE_LIBRARY_ARCHITECTURE STREQUAL "x86_64-linux-gnux32") + set(_java_libarch "x32" "amd64" "i386") + else() + set(_java_libarch "amd64" "i386") + endif() + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") + set(_java_libarch "i386") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^alpha") + set(_java_libarch "alpha") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm") + # Subdir is "arm" for both big-endian (arm) and little-endian (armel). + set(_java_libarch "arm" "aarch32") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^mips") + # mips* machines are bi-endian mostly so processor does not tell + # endianness of the underlying system. + set(_java_libarch "${CMAKE_SYSTEM_PROCESSOR}" + "mips" "mipsel" "mipseb" "mipsr6" "mipsr6el" + "mips64" "mips64el" "mips64r6" "mips64r6el" + "mipsn32" "mipsn32el" "mipsn32r6" "mipsn32r6el") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64le") + set(_java_libarch "ppc64" "ppc64le") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64") + set(_java_libarch "ppc64" "ppc") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)") + set(_java_libarch "ppc" "ppc64") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^sparc") + # Both flavours can run on the same processor + set(_java_libarch "${CMAKE_SYSTEM_PROCESSOR}" "sparc" "sparcv9") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(parisc|hppa)") + set(_java_libarch "parisc" "parisc64") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^s390") + # s390 binaries can run on s390x machines + set(_java_libarch "${CMAKE_SYSTEM_PROCESSOR}" "s390" "s390x") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^sh") + set(_java_libarch "sh") + else() + set(_java_libarch "${CMAKE_SYSTEM_PROCESSOR}") + endif() + + # Append default list architectures if CMAKE_SYSTEM_PROCESSOR was empty or + # system is non-Linux (where the code above has not been well tested) + if(NOT _java_libarch OR NOT (CMAKE_SYSTEM_NAME MATCHES "Linux")) + list(APPEND _java_libarch "i386" "amd64" "ppc") + endif() + + # Sometimes ${CMAKE_SYSTEM_PROCESSOR} is added to the list to prefer + # current value to a hardcoded list. Remove possible duplicates. + list(REMOVE_DUPLICATES _java_libarch) + + foreach(_path ${ARGN}) + if(_path MATCHES "{libarch}") + foreach(_libarch ${_java_libarch}) + string(REPLACE "{libarch}" "${_libarch}" _newpath "${_path}") + if(EXISTS ${_newpath}) + list(APPEND ${_var} "${_newpath}") + endif() + endforeach() + else() + if(EXISTS ${_path}) + list(APPEND ${_var} "${_path}") + endif() + endif() + endforeach() +endmacro() + +include(CMakeFindJavaCommon) + +# Save CMAKE_FIND_FRAMEWORK +if(DEFINED CMAKE_FIND_FRAMEWORK) + set(_JNI_CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK}) +else() + unset(_JNI_CMAKE_FIND_FRAMEWORK) +endif() + +if(_JAVA_HOME_EXPLICIT) + set(CMAKE_FIND_FRAMEWORK NEVER) +endif() + +set(JAVA_AWT_LIBRARY_DIRECTORIES) +if(_JAVA_HOME) + JAVA_APPEND_LIBRARY_DIRECTORIES(JAVA_AWT_LIBRARY_DIRECTORIES + ${_JAVA_HOME}/jre/lib/{libarch} + ${_JAVA_HOME}/jre/lib + ${_JAVA_HOME}/lib/{libarch} + ${_JAVA_HOME}/lib + ${_JAVA_HOME} + ) +endif() + +if (WIN32) + set (_JNI_HINTS) + execute_process(COMMAND REG QUERY HKLM\\SOFTWARE\\JavaSoft\\JDK /f "." /k + RESULT_VARIABLE _JNI_RESULT + OUTPUT_VARIABLE _JNI_VERSIONS + ERROR_QUIET) + if (NOT _JNI_RESULT) + string (REGEX MATCHALL "HKEY_LOCAL_MACHINE\\\\SOFTWARE\\\\JavaSoft\\\\JDK\\\\[0-9.]+" _JNI_VERSIONS "${_JNI_VERSIONS}") + if (_JNI_VERSIONS) + # sort versions. Most recent first + ## handle version 9 apart from other versions to get correct ordering + set (_JNI_V9 ${_JNI_VERSIONS}) + list (FILTER _JNI_VERSIONS EXCLUDE REGEX "JDK\\\\9") + list (SORT _JNI_VERSIONS) + list (REVERSE _JNI_VERSIONS) + list (FILTER _JNI_V9 INCLUDE REGEX "JDK\\\\9") + list (SORT _JNI_V9) + list (REVERSE _JNI_V9) + list (APPEND _JNI_VERSIONS ${_JNI_V9}) + foreach (_JNI_HINT IN LISTS _JNI_VERSIONS) + list(APPEND _JNI_HINTS "[${_JNI_HINT};JavaHome]") + endforeach() + endif() + endif() + + foreach (_JNI_HINT IN LISTS _JNI_HINTS) + list(APPEND JAVA_AWT_LIBRARY_DIRECTORIES "${_JNI_HINT}/lib") + endforeach() + + get_filename_component(java_install_version + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit;CurrentVersion]" NAME) + + list(APPEND JAVA_AWT_LIBRARY_DIRECTORIES + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\11;JavaHome]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\10;JavaHome]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.9;JavaHome]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.8;JavaHome]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.7;JavaHome]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.6;JavaHome]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.5;JavaHome]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.4;JavaHome]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.3;JavaHome]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\${java_install_version};JavaHome]/lib" + ) +endif() + +JAVA_APPEND_LIBRARY_DIRECTORIES(JAVA_AWT_LIBRARY_DIRECTORIES + /usr/lib/jvm/java/lib + /usr/lib/java/jre/lib/{libarch} + /usr/lib/jvm/jre/lib/{libarch} + /usr/local/lib/java/jre/lib/{libarch} + /usr/local/share/java/jre/lib/{libarch} + # Debian specific paths for default JVM + /usr/lib/jvm/default-java/jre/lib/{libarch} + /usr/lib/jvm/default-java/jre/lib + /usr/lib/jvm/default-java/lib + # Arch Linux specific paths for default JVM + /usr/lib/jvm/default/jre/lib/{libarch} + /usr/lib/jvm/default/lib/{libarch} + # SuSE specific paths for default JVM + /usr/lib64/jvm/java/jre/lib/{libarch} + /usr/lib64/jvm/jre/lib/{libarch} + /usr/lib/jvm/java-11-openjdk-{libarch}/jre/lib/{libarch} # Ubuntu 18.04 LTS + /usr/lib/jvm/java-11-oracle/lib/{libarch} + /usr/lib/jvm/java-10-openjdk-{libarch}/jre/lib/{libarch} + /usr/lib/jvm/java-10-oracle/lib/{libarch} + /usr/lib/jvm/java-9-openjdk-{libarch}/jre/lib/{libarch} + /usr/lib/jvm/java-9-oracle/lib/{libarch} + /usr/lib/jvm/java-8-openjdk-{libarch}/jre/lib/{libarch} # Ubuntu 15.10 + /usr/lib/jvm/java-8-oracle/lib/{libarch} + # Ubuntu specific paths for default JVM + /usr/lib/jvm/java-7-openjdk-{libarch}/jre/lib/{libarch} # Ubuntu 15.10 + /usr/lib/jvm/java-7-oracle/lib/{libarch} + /usr/lib/jvm/java-6-openjdk-{libarch}/jre/lib/{libarch} # Ubuntu 15.10 + /usr/lib/jvm/java-6-oracle/lib/{libarch} + /usr/lib/jvm/java-6-sun/jre/lib/{libarch} + /usr/lib/jvm/java-6-sun-1.6.0.00/jre/lib/{libarch} # can this one be removed according to #8821 ? Alex + /usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0/jre/lib/{libarch} # fedora + /usr/lib/jvm/java-6-openjdk/jre/lib/{libarch} + # OpenBSD specific paths for default JVM + /usr/local/jdk-1.8.0/jre/lib/{libarch} + /usr/local/jre-1.8.0/lib/{libarch} + /usr/local/jdk-1.7.0/jre/lib/{libarch} + /usr/local/jre-1.7.0/lib/{libarch} + /usr/local/jdk-1.6.0/jre/lib/{libarch} + /usr/local/jre-1.6.0/lib/{libarch} + ) + +set(JAVA_JVM_LIBRARY_DIRECTORIES) +foreach(dir ${JAVA_AWT_LIBRARY_DIRECTORIES}) + list(APPEND JAVA_JVM_LIBRARY_DIRECTORIES + "${dir}" + "${dir}/client" + "${dir}/server" + # IBM SDK, Java Technology Edition, specific paths + "${dir}/j9vm" + "${dir}/default" + ) +endforeach() + +set(JAVA_AWT_INCLUDE_DIRECTORIES) +if(_JAVA_HOME) + list(APPEND JAVA_AWT_INCLUDE_DIRECTORIES ${_JAVA_HOME}/include) +endif() +if (WIN32) + foreach (_JNI_HINT IN LISTS _JNI_HINTS) + list(APPEND JAVA_AWT_INCLUDE_DIRECTORIES "${_JNI_HINT}/include") + endforeach() + list(APPEND JAVA_AWT_INCLUDE_DIRECTORIES + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\11;JavaHome]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\10;JavaHome]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.9;JavaHome]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.8;JavaHome]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.7;JavaHome]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.6;JavaHome]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.5;JavaHome]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.4;JavaHome]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.3;JavaHome]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\${java_install_version};JavaHome]/include" + ) +endif() + +JAVA_APPEND_LIBRARY_DIRECTORIES(JAVA_AWT_INCLUDE_DIRECTORIES + /usr/lib/java/include + /usr/local/lib/java/include + /usr/lib/jvm/java/include + /usr/lib/jvm/java-11-openjdk-{libarch}/include + /usr/lib/jvm/java-11-oracle/include + /usr/lib/jvm/java-10-openjdk-{libarch}/include + /usr/lib/jvm/java-10-oracle/include + /usr/lib/jvm/java-9-openjdk-{libarch}/include + /usr/lib/jvm/java-9-oracle/include + /usr/lib/jvm/java-8-openjdk-{libarch}/include + /usr/lib/jvm/java-8-oracle/include + /usr/lib/jvm/java-7-openjdk-{libarch}/include + /usr/lib/jvm/java-7-oracle/include + /usr/lib/jvm/java-6-openjdk-{libarch}/include + /usr/lib/jvm/java-6-openjdk/include + /usr/lib/jvm/java-6-oracle/include + /usr/lib/jvm/java-6-sun/include + /usr/local/share/java/include + # Debian specific path for default JVM + /usr/lib/jvm/default-java/include + # Arch specific path for default JVM + /usr/lib/jvm/default/include + # OpenBSD specific path for default JVM + /usr/local/jdk-1.8.0/include + /usr/local/jdk-1.7.0/include + /usr/local/jdk-1.6.0/include + # SuSE specific paths for default JVM + /usr/lib64/jvm/java/include + ) + +foreach(JAVA_PROG "${JAVA_RUNTIME}" "${JAVA_COMPILE}" "${JAVA_ARCHIVE}") + get_filename_component(jpath "${JAVA_PROG}" PATH) + foreach(JAVA_INC_PATH ../include ../java/include ../share/java/include) + if(EXISTS ${jpath}/${JAVA_INC_PATH}) + list(APPEND JAVA_AWT_INCLUDE_DIRECTORIES "${jpath}/${JAVA_INC_PATH}") + endif() + endforeach() + foreach(JAVA_LIB_PATH + ../lib ../jre/lib ../jre/lib/i386 + ../java/lib ../java/jre/lib ../java/jre/lib/i386 + ../share/java/lib ../share/java/jre/lib ../share/java/jre/lib/i386) + if(EXISTS ${jpath}/${JAVA_LIB_PATH}) + list(APPEND JAVA_AWT_LIBRARY_DIRECTORIES "${jpath}/${JAVA_LIB_PATH}") + endif() + endforeach() +endforeach() + +if(APPLE) + if(CMAKE_FIND_FRAMEWORK STREQUAL "ONLY") + set(_JNI_SEARCHES FRAMEWORK) + elseif(CMAKE_FIND_FRAMEWORK STREQUAL "NEVER") + set(_JNI_SEARCHES NORMAL) + elseif(CMAKE_FIND_FRAMEWORK STREQUAL "LAST") + set(_JNI_SEARCHES NORMAL FRAMEWORK) + else() + set(_JNI_SEARCHES FRAMEWORK NORMAL) + endif() + set(_JNI_FRAMEWORK_JVM NAMES JavaVM) + set(_JNI_FRAMEWORK_JAWT "${_JNI_FRAMEWORK_JVM}") +else() + set(_JNI_SEARCHES NORMAL) +endif() + +set(_JNI_NORMAL_JVM + NAMES jvm + PATHS ${JAVA_JVM_LIBRARY_DIRECTORIES} + ) + +set(_JNI_NORMAL_JAWT + NAMES jawt + PATHS ${JAVA_AWT_LIBRARY_DIRECTORIES} + ) + +foreach(search ${_JNI_SEARCHES}) + find_library(JAVA_JVM_LIBRARY ${_JNI_${search}_JVM}) + find_library(JAVA_AWT_LIBRARY ${_JNI_${search}_JAWT}) + if(JAVA_JVM_LIBRARY) + break() + endif() +endforeach() +unset(_JNI_SEARCHES) +unset(_JNI_FRAMEWORK_JVM) +unset(_JNI_FRAMEWORK_JAWT) +unset(_JNI_NORMAL_JVM) +unset(_JNI_NORMAL_JAWT) + +# Find headers matching the library. +if("${JAVA_JVM_LIBRARY};${JAVA_AWT_LIBRARY};" MATCHES "(/JavaVM.framework|-framework JavaVM);") + set(CMAKE_FIND_FRAMEWORK ONLY) +else() + set(CMAKE_FIND_FRAMEWORK NEVER) +endif() + +# add in the include path +find_path(JAVA_INCLUDE_PATH jni.h + ${JAVA_AWT_INCLUDE_DIRECTORIES} +) + +find_path(JAVA_INCLUDE_PATH2 NAMES jni_md.h jniport.h + PATHS + ${JAVA_INCLUDE_PATH} + ${JAVA_INCLUDE_PATH}/darwin + ${JAVA_INCLUDE_PATH}/win32 + ${JAVA_INCLUDE_PATH}/linux + ${JAVA_INCLUDE_PATH}/freebsd + ${JAVA_INCLUDE_PATH}/openbsd + ${JAVA_INCLUDE_PATH}/solaris + ${JAVA_INCLUDE_PATH}/hp-ux + ${JAVA_INCLUDE_PATH}/alpha + ${JAVA_INCLUDE_PATH}/aix +) + +# Restore CMAKE_FIND_FRAMEWORK +if(DEFINED _JNI_CMAKE_FIND_FRAMEWORK) + set(CMAKE_FIND_FRAMEWORK ${_JNI_CMAKE_FIND_FRAMEWORK}) + unset(_JNI_CMAKE_FIND_FRAMEWORK) +else() + unset(CMAKE_FIND_FRAMEWORK) +endif() + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(JNI DEFAULT_MSG JAVA_AWT_LIBRARY + JAVA_JVM_LIBRARY + JAVA_INCLUDE_PATH + JAVA_INCLUDE_PATH2) + +mark_as_advanced( + JAVA_AWT_LIBRARY + JAVA_JVM_LIBRARY + JAVA_INCLUDE_PATH + JAVA_INCLUDE_PATH2 +) + +set(JNI_LIBRARIES + ${JAVA_AWT_LIBRARY} + ${JAVA_JVM_LIBRARY} +) + +set(JNI_INCLUDE_DIRS + ${JAVA_INCLUDE_PATH} + ${JAVA_INCLUDE_PATH2} +) diff --git a/cmake/Modules/FindJava.cmake b/cmake/Modules/FindJava.cmake new file mode 100644 index 0000000..46c4293 --- /dev/null +++ b/cmake/Modules/FindJava.cmake @@ -0,0 +1,359 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindJava +-------- + +Find Java + +This module finds if Java is installed and determines where the +include files and libraries are. The caller may set variable ``JAVA_HOME`` +to specify a Java installation prefix explicitly. + +See also the :module:`FindJNI` module to find Java Native Interface (JNI). + +Specify one or more of the following components as you call this find module. See example below. + +:: + + Runtime = Java Runtime Environment used to execute Java byte-compiled applications + Development = Development tools (java, javac, javah, jar and javadoc), includes Runtime component + IdlJ = Interface Description Language (IDL) to Java compiler + JarSigner = Signer and verifier tool for Java Archive (JAR) files + + +This module sets the following result variables: + +:: + + Java_JAVA_EXECUTABLE = the full path to the Java runtime + Java_JAVAC_EXECUTABLE = the full path to the Java compiler + Java_JAVAH_EXECUTABLE = the full path to the Java header generator + Java_JAVADOC_EXECUTABLE = the full path to the Java documentation generator + Java_IDLJ_EXECUTABLE = the full path to the Java idl compiler + Java_JAR_EXECUTABLE = the full path to the Java archiver + Java_JARSIGNER_EXECUTABLE = the full path to the Java jar signer + Java_VERSION_STRING = Version of java found, eg. 1.6.0_12 + Java_VERSION_MAJOR = The major version of the package found. + Java_VERSION_MINOR = The minor version of the package found. + Java_VERSION_PATCH = The patch version of the package found. + Java_VERSION_TWEAK = The tweak version of the package found (after '_') + Java_VERSION = This is set to: $major[.$minor[.$patch[.$tweak]]] + + + +The minimum required version of Java can be specified using the +:command:`find_package` syntax, e.g. + +.. code-block:: cmake + + find_package(Java 1.8) + +NOTE: ``${Java_VERSION}`` and ``${Java_VERSION_STRING}`` are not guaranteed to +be identical. For example some java version may return: +``Java_VERSION_STRING = 1.8.0_17`` and ``Java_VERSION = 1.8.0.17`` + +another example is the Java OEM, with: ``Java_VERSION_STRING = 1.8.0-oem`` +and ``Java_VERSION = 1.8.0`` + +For these components the following variables are set: + +:: + + Java_FOUND - TRUE if all components are found. + Java__FOUND - TRUE if is found. + + + +Example Usages: + +:: + + find_package(Java) + find_package(Java 1.8 REQUIRED) + find_package(Java COMPONENTS Runtime) + find_package(Java COMPONENTS Development) +#]=======================================================================] + +include(CMakeFindJavaCommon) + +# The HINTS option should only be used for values computed from the system. +set(_JAVA_HINTS) +if(_JAVA_HOME) + list(APPEND _JAVA_HINTS ${_JAVA_HOME}/bin) +endif() +if (WIN32) + macro (_JAVA_GET_INSTALLED_VERSIONS _KIND) + execute_process(COMMAND REG QUERY HKLM\\SOFTWARE\\JavaSoft\\${_KIND} + RESULT_VARIABLE _JAVA_RESULT + OUTPUT_VARIABLE _JAVA_VERSIONS + ERROR_QUIET) + if (NOT _JAVA_RESULT) + string (REGEX MATCHALL "HKEY_LOCAL_MACHINE\\\\SOFTWARE\\\\JavaSoft\\\\${_KIND}\\\\[0-9.]+" _JAVA_VERSIONS "${_JAVA_VERSIONS}") + if (_JAVA_VERSIONS) + # sort versions. Most recent first + ## handle version 9 apart from other versions to get correct ordering + set (_JAVA_V9 ${_JAVA_VERSIONS}) + list (FILTER _JAVA_VERSIONS EXCLUDE REGEX "${_KIND}\\\\9") + list (SORT _JAVA_VERSIONS) + list (REVERSE _JAVA_VERSIONS) + list (FILTER _JAVA_V9 INCLUDE REGEX "${_KIND}\\\\9") + list (SORT _JAVA_V9) + list (REVERSE _JAVA_V9) + list (APPEND _JAVA_VERSIONS ${_JAVA_V9}) + foreach (_JAVA_HINT IN LISTS _JAVA_VERSIONS) + list(APPEND _JAVA_HINTS "[${_JAVA_HINT};JavaHome]/bin") + endforeach() + endif() + endif() + endmacro() + + # search for installed versions for version 9 and upper + _JAVA_GET_INSTALLED_VERSIONS("JDK") + _JAVA_GET_INSTALLED_VERSIONS("JRE") + + list(APPEND _JAVA_HINTS + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.9;JavaHome]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.8;JavaHome]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.7;JavaHome]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.6;JavaHome]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.5;JavaHome]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.4;JavaHome]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.3;JavaHome]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\1.9;JavaHome]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\1.8;JavaHome]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\1.7;JavaHome]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\1.6;JavaHome]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\1.5;JavaHome]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\1.4;JavaHome]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\1.3;JavaHome]/bin" + ) +endif() + +# Hard-coded guesses should still go in PATHS. This ensures that the user +# environment can always override hard guesses. +set(_JAVA_PATHS + /usr/lib/java/bin + /usr/share/java/bin + /usr/local/java/bin + /usr/local/java/share/bin + /usr/java/j2sdk1.4.2_04 + /usr/lib/j2sdk1.4-sun/bin + /usr/java/j2sdk1.4.2_09/bin + /usr/lib/j2sdk1.5-sun/bin + /opt/sun-jdk-1.5.0.04/bin + /usr/local/jdk-1.7.0/bin + /usr/local/jdk-1.6.0/bin + ) +find_program(Java_JAVA_EXECUTABLE + NAMES java + HINTS ${_JAVA_HINTS} + PATHS ${_JAVA_PATHS} +) + +if(Java_JAVA_EXECUTABLE) + execute_process(COMMAND "${Java_JAVA_EXECUTABLE}" -version + RESULT_VARIABLE res + OUTPUT_VARIABLE var + ERROR_VARIABLE var # sun-java output to stderr + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_STRIP_TRAILING_WHITESPACE) + if( res ) + if(var MATCHES "Unable to locate a Java Runtime to invoke|No Java runtime present, requesting install") + set(Java_JAVA_EXECUTABLE Java_JAVA_EXECUTABLE-NOTFOUND) + elseif(${Java_FIND_REQUIRED}) + message( FATAL_ERROR "Error executing java -version" ) + else() + message( STATUS "Warning, could not run java -version") + endif() + else() + # Extract version components (up to 4 levels) from "java -version" output. + set(_java_version_regex [[(([0-9]+)(\.([0-9]+)(\.([0-9]+)(_([0-9]+))?)?)?.*)]]) + if(var MATCHES "java version \"${_java_version_regex}\"") + # Sun, GCJ, older OpenJDK + set(Java_VERSION_STRING "${CMAKE_MATCH_1}") + set(Java_VERSION_MAJOR "${CMAKE_MATCH_2}") + if (CMAKE_MATCH_4) + set(Java_VERSION_MINOR "${CMAKE_MATCH_4}") + else() + set(Java_VERSION_MINOR 0) + endif() + if (CMAKE_MATCH_6) + set(Java_VERSION_PATCH "${CMAKE_MATCH_6}") + else() + set(Java_VERSION_PATCH 0) + endif() + set(Java_VERSION_TWEAK "${CMAKE_MATCH_8}") + elseif(var MATCHES "openjdk version \"${_java_version_regex}\"") + # OpenJDK + set(Java_VERSION_STRING "${CMAKE_MATCH_1}") + set(Java_VERSION_MAJOR "${CMAKE_MATCH_2}") + if (CMAKE_MATCH_4) + set(Java_VERSION_MINOR "${CMAKE_MATCH_4}") + else() + set(Java_VERSION_MINOR 0) + endif() + if (CMAKE_MATCH_6) + set(Java_VERSION_PATCH "${CMAKE_MATCH_6}") + else() + set(Java_VERSION_PATCH 0) + endif() + set(Java_VERSION_TWEAK "${CMAKE_MATCH_8}") + elseif(var MATCHES "openjdk version \"([0-9]+)-[A-Za-z]+\"") + # OpenJDK 9 early access builds or locally built + set(Java_VERSION_STRING "1.${CMAKE_MATCH_1}.0") + set(Java_VERSION_MAJOR "1") + set(Java_VERSION_MINOR "${CMAKE_MATCH_1}") + set(Java_VERSION_PATCH "0") + set(Java_VERSION_TWEAK "") + elseif(var MATCHES "java full version \"kaffe-${_java_version_regex}\"") + # Kaffe style + set(Java_VERSION_STRING "${CMAKE_MATCH_1}") + set(Java_VERSION_MAJOR "${CMAKE_MATCH_2}") + set(Java_VERSION_MINOR "${CMAKE_MATCH_4}") + set(Java_VERSION_PATCH "${CMAKE_MATCH_6}") + set(Java_VERSION_TWEAK "${CMAKE_MATCH_8}") + else() + if(NOT Java_FIND_QUIETLY) + string(REPLACE "\n" "\n " ver_msg "\n${var}") + message(WARNING "Java version not recognized:${ver_msg}\nPlease report.") + endif() + set(Java_VERSION_STRING "") + set(Java_VERSION_MAJOR "") + set(Java_VERSION_MINOR "") + set(Java_VERSION_PATCH "") + set(Java_VERSION_TWEAK "") + endif() + set(Java_VERSION "${Java_VERSION_MAJOR}") + if(NOT "x${Java_VERSION}" STREQUAL "x") + foreach(c MINOR PATCH TWEAK) + if(NOT "x${Java_VERSION_${c}}" STREQUAL "x") + string(APPEND Java_VERSION ".${Java_VERSION_${c}}") + else() + break() + endif() + endforeach() + endif() + endif() + +endif() + + +find_program(Java_JAR_EXECUTABLE + NAMES jar + HINTS ${_JAVA_HINTS} + PATHS ${_JAVA_PATHS} +) + +find_program(Java_JAVAC_EXECUTABLE + NAMES javac + HINTS ${_JAVA_HINTS} + PATHS ${_JAVA_PATHS} +) + +find_program(Java_JAVAH_EXECUTABLE + NAMES javah + HINTS ${_JAVA_HINTS} + PATHS ${_JAVA_PATHS} +) + +find_program(Java_JAVADOC_EXECUTABLE + NAMES javadoc + HINTS ${_JAVA_HINTS} + PATHS ${_JAVA_PATHS} +) + +find_program(Java_IDLJ_EXECUTABLE + NAMES idlj + HINTS ${_JAVA_HINTS} + PATHS ${_JAVA_PATHS} +) + +find_program(Java_JARSIGNER_EXECUTABLE + NAMES jarsigner + HINTS ${_JAVA_HINTS} + PATHS ${_JAVA_PATHS} +) + +include(FindPackageHandleStandardArgs) +if(Java_FIND_COMPONENTS) + set(_JAVA_REQUIRED_VARS) + foreach(component ${Java_FIND_COMPONENTS}) + # User just want to execute some Java byte-compiled + If(component STREQUAL "Runtime") + list(APPEND _JAVA_REQUIRED_VARS Java_JAVA_EXECUTABLE) + if(Java_JAVA_EXECUTABLE) + set(Java_Runtime_FOUND TRUE) + endif() + elseif(component STREQUAL "Development") + list(APPEND _JAVA_REQUIRED_VARS Java_JAVA_EXECUTABLE Java_JAVAC_EXECUTABLE + Java_JAR_EXECUTABLE Java_JAVADOC_EXECUTABLE) + if(Java_VERSION VERSION_LESS "10") + list(APPEND _JAVA_REQUIRED_VARS Java_JAVAH_EXECUTABLE) + if(Java_JAVA_EXECUTABLE AND Java_JAVAC_EXECUTABLE + AND Java_JAVAH_EXECUTABLE AND Java_JAR_EXECUTABLE AND Java_JAVADOC_EXECUTABLE) + set(Java_Development_FOUND TRUE) + endif() + else() + if(Java_JAVA_EXECUTABLE AND Java_JAVAC_EXECUTABLE + AND Java_JAR_EXECUTABLE AND Java_JAVADOC_EXECUTABLE) + set(Java_Development_FOUND TRUE) + endif() + endif() + elseif(component STREQUAL "IdlJ") + list(APPEND _JAVA_REQUIRED_VARS Java_IDLJ_EXECUTABLE) + if(Java_IDLJ_EXECUTABLE) + set(Java_IdlJ_FOUND TRUE) + endif() + elseif(component STREQUAL "JarSigner") + list(APPEND _JAVA_REQUIRED_VARS Java_JARSIGNER_EXECUTABLE) + if(Java_JARSIGNER_EXECUTABLE) + set(Java_JarSigner_FOUND TRUE) + endif() + else() + message(FATAL_ERROR "Comp: ${component} is not handled") + endif() + endforeach() + list (REMOVE_DUPLICATES _JAVA_REQUIRED_VARS) + find_package_handle_standard_args(Java + REQUIRED_VARS ${_JAVA_REQUIRED_VARS} HANDLE_COMPONENTS + VERSION_VAR Java_VERSION + ) + if(Java_FOUND) + foreach(component ${Java_FIND_COMPONENTS}) + set(Java_${component}_FOUND TRUE) + endforeach() + endif() +else() + # Check for Development + if(Java_VERSION VERSION_LESS "10") + find_package_handle_standard_args(Java + REQUIRED_VARS Java_JAVA_EXECUTABLE Java_JAR_EXECUTABLE Java_JAVAC_EXECUTABLE + Java_JAVAH_EXECUTABLE Java_JAVADOC_EXECUTABLE + VERSION_VAR Java_VERSION_STRING + ) + else() + find_package_handle_standard_args(Java + REQUIRED_VARS Java_JAVA_EXECUTABLE Java_JAR_EXECUTABLE Java_JAVAC_EXECUTABLE + Java_JAVADOC_EXECUTABLE + VERSION_VAR Java_VERSION_STRING + ) + endif() +endif() + + +mark_as_advanced( + Java_JAVA_EXECUTABLE + Java_JAR_EXECUTABLE + Java_JAVAC_EXECUTABLE + Java_JAVAH_EXECUTABLE + Java_JAVADOC_EXECUTABLE + Java_IDLJ_EXECUTABLE + Java_JARSIGNER_EXECUTABLE + ) + +# LEGACY +set(JAVA_RUNTIME ${Java_JAVA_EXECUTABLE}) +set(JAVA_ARCHIVE ${Java_JAR_EXECUTABLE}) +set(JAVA_COMPILE ${Java_JAVAC_EXECUTABLE}) diff --git a/cmake/Modules/FindLibIberty.cmake b/cmake/Modules/FindLibIberty.cmake new file mode 100644 index 0000000..e291c4c --- /dev/null +++ b/cmake/Modules/FindLibIberty.cmake @@ -0,0 +1,40 @@ +# - Find Iberty +# This module finds libiberty. +# +# It sets the following variables: +# IBERTY_LIBRARIES - The libiberty library to link against. + +# For Debian <= wheezy, use libiberty_pic.a from binutils-dev +# For Debian >= jessie, use libiberty.a from libiberty-dev +# For all RHEL/Fedora, use libiberty.a from binutils-devel +FIND_LIBRARY( IBERTY_LIBRARIES + NAMES iberty_pic iberty + HINTS ${IBERTY_LIBRARIES} + PATHS + /usr/lib + /usr/lib64 + /usr/local/lib + /usr/local/lib64 + /opt/local/lib + /opt/local/lib64 + /sw/lib + ENV LIBRARY_PATH + ENV LD_LIBRARY_PATH + ) + +IF (IBERTY_LIBRARIES) + + # show which libiberty was found only if not quiet + MESSAGE( STATUS "Found libiberty: ${IBERTY_LIBRARIES}") + + SET(IBERTY_FOUND TRUE) + +ELSE (IBERTY_LIBRARIES) + + IF (IBERTY_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find libiberty. Try to install binutil-devel?") + ELSE() + MESSAGE(STATUS "Could not find libiberty; downloading binutils and building PIC libiberty.") + ENDIF (IBERTY_FIND_REQUIRED) + +ENDIF (IBERTY_LIBRARIES) diff --git a/cmake/Modules/GitRevision.cmake b/cmake/Modules/GitRevision.cmake new file mode 100644 index 0000000..c18f986 --- /dev/null +++ b/cmake/Modules/GitRevision.cmake @@ -0,0 +1,18 @@ +message(STATUS "Resolving GIT Version") +set(GIT_REVISION "") +find_package(Git) +if(GIT_FOUND) + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + WORKING_DIRECTORY "${local_dir}" + OUTPUT_VARIABLE GIT_REVISION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + message(STATUS "GIT hash: ${GIT_REVISION}") +else() + message(STATUS "GIT not found") +endif() + + + diff --git a/cmake/Modules/ProjectUtils.cmake b/cmake/Modules/ProjectUtils.cmake new file mode 100644 index 0000000..b4d6923 --- /dev/null +++ b/cmake/Modules/ProjectUtils.cmake @@ -0,0 +1,16 @@ +macro(MACRO_ENSURE_OUT_OF_SOURCE_BUILD MSG) + string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" insource) + get_filename_component(PARENTDIR ${CMAKE_SOURCE_DIR} PATH) + string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${PARENTDIR}" insourcesubdir) + if(insource OR insourcesubdir) + message(FATAL_ERROR "${MSG}") + endif(insource OR insourcesubdir) +endmacro(MACRO_ENSURE_OUT_OF_SOURCE_BUILD) + +function(UTILS_LIST_PREPEND var prefix) + set(_collector) + foreach(n ${ARGN}) + list(APPEND _collector "${prefix}${n}") + endforeach() + set(${var} ${_collector} PARENT_SCOPE) +endfunction(UTILS_LIST_PREPEND) diff --git a/cmake/Modules/TestQSortR.c b/cmake/Modules/TestQSortR.c new file mode 100644 index 0000000..2bf7fbe --- /dev/null +++ b/cmake/Modules/TestQSortR.c @@ -0,0 +1,5 @@ +#include +int main(int argc, char **argv) { + qsort_r(0, 0, 0, 0, 0); + return 0; +} diff --git a/cmake/Modules/TestQsortR.cmake b/cmake/Modules/TestQsortR.cmake new file mode 100644 index 0000000..3a18ed0 --- /dev/null +++ b/cmake/Modules/TestQsortR.cmake @@ -0,0 +1,7 @@ +if (NOT DEFINED HAVE_QSORT_R) + try_compile(HAVE_QSORT_R + "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/" + "${CMAKE_CURRENT_LIST_DIR}/TestQSortR.c") +endif() + + diff --git a/cmake/Modules/UploadPPA.cmake b/cmake/Modules/UploadPPA.cmake new file mode 100644 index 0000000..2bed362 --- /dev/null +++ b/cmake/Modules/UploadPPA.cmake @@ -0,0 +1,361 @@ +## -*- mode:cmake; coding:utf-8; -*- +# Copyright (c) 2010 Daniel Pfeifer +# Changes Copyright (c) 2011 2012 Rüdiger Sonderfeld +# +# UploadPPA.cmake is free software. It comes without any warranty, +# to the extent permitted by applicable law. You can redistribute it +# and/or modify it under the terms of the Do What The Fuck You Want +# To Public License, Version 2, as published by Sam Hocevar. See +# http://sam.zoy.org/wtfpl/COPYING for more details. +# +## +# Documentation +# +# This CMake module uploads a project to a PPA. It creates all the files +# necessary (similar to CPack) and uses debuild(1) and dput(1) to create the +# package and upload it to a PPA. A PPA is a Personal Package Archive and can +# be used by Debian/Ubuntu or other apt/deb based distributions to install and +# update packages from a remote repository. +# Canonicals Launchpad (http://launchpad.net) is usually used to host PPAs. +# See https://help.launchpad.net/Packaging/PPA for further information +# about PPAs. +# +# UploadPPA.cmake uses similar settings to CPack and the CPack DEB Generator. +# Additionally the following variables are used +# +# CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS to specify build dependencies +# (cmake is added as default) +# CPACK_DEBIAN_RESOURCE_FILE_CHANGELOG should point to a file containing the +# changelog in debian format. If not set it checks whether a file +# debian/changelog exists in the source directory or creates a simply changelog +# file. +# CPACK_DEBIAN_UPDATE_CHANGELOG if set to True then UploadPPA.cmake adds a new +# entry to the changelog with the current version number and distribution name +# (lsb_release -c is used). This can be useful because debuild uses the latest +# version number from the changelog and the version number set in +# CPACK_PACKAGE_VERSION. If they mismatch the creation of the package fails. +# +## A.Hoarau : CHANGELOG_MESSAGE can be used to pass a custom changelog message +# Check packages +# +# ./configure -DENABLE_PPA=On +# make dput +# cd build/Debian/${DISTRI} +# dpkg-source -x vobsub2srt_1.0pre4-ppa1.dsc +# cd vobsub2srt-1.0pre4/ +# debuild -i -us -uc -sa -b +# +# Check the lintian warnings! +# +## +# TODO +# I plan to add support for git dch (from git-buildpackage) to auto generate +# the changelog. +## + +find_program(DEBUILD_EXECUTABLE debuild) +find_program(DPUT_EXECUTABLE dput) + +if(NOT DEBUILD_EXECUTABLE OR NOT DPUT_EXECUTABLE) + message(WARNING "Debuild or dput not installed, please run sudo apt-get install devscripts") + return() +endif(NOT DEBUILD_EXECUTABLE OR NOT DPUT_EXECUTABLE) + + +if(NOT PROJECT_PPA_DISTRIB_TARGET) +execute_process( + COMMAND lsb_release -cs + OUTPUT_VARIABLE DISTRI + OUTPUT_STRIP_TRAILING_WHITESPACE) + set(PROJECT_PPA_DISTRIB_TARGET ${DISTRI}) + message(STATUS "PROJECT_PPA_DISTRIB_TARGET NOT provided, so using system settings : ${DISTRI}") +endif() + +foreach(DISTRI ${PROJECT_PPA_DISTRIB_TARGET}) +message(STATUS "Building for ${DISTRI}") + +# Strip "-dirty" flag from package version. +# It can be added by, e.g., git describe but it causes trouble with debuild etc. +string(REPLACE "-dirty" "" CPACK_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION}) +message(STATUS "version: ${CPACK_PACKAGE_VERSION}") + +# DEBIAN/control +# debian policy enforce lower case for package name +# Package: (mandatory) +IF(NOT CPACK_DEBIAN_PACKAGE_NAME) + STRING(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_DEBIAN_PACKAGE_NAME) +ENDIF(NOT CPACK_DEBIAN_PACKAGE_NAME) + +# Section: (recommended) +IF(NOT CPACK_DEBIAN_PACKAGE_SECTION) + SET(CPACK_DEBIAN_PACKAGE_SECTION "devel") +ENDIF(NOT CPACK_DEBIAN_PACKAGE_SECTION) + +# Priority: (recommended) +IF(NOT CPACK_DEBIAN_PACKAGE_PRIORITY) + SET(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") +ENDIF(NOT CPACK_DEBIAN_PACKAGE_PRIORITY) + +if(NOT CPACK_DEBIAN_PACKAGE_MAINTAINER) + set(CPACK_DEBIAN_PACKAGE_MAINTAINER ${CPACK_PACKAGE_CONTACT}) +endif() + +if(NOT CPACK_PACKAGE_DESCRIPTION AND EXISTS ${CPACK_PACKAGE_DESCRIPTION_FILE}) + file(STRINGS ${CPACK_PACKAGE_DESCRIPTION_FILE} DESC_LINES) + foreach(LINE ${DESC_LINES}) + set(deb_long_description "${deb_long_description} ${LINE}\n") + endforeach(LINE ${DESC_LINES}) +else() + # add space before each line + string(REPLACE "\n" "\n " deb_long_description " ${CPACK_PACKAGE_DESCRIPTION}") +endif() + +if(PPA_DEBIAN_VERSION) + set(DEBIAN_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}-${PPA_DEBIAN_VERSION}~${DISTRI}1") +elseif(NOT PPA_DEBIAN_VERSION AND NOT PROJECT_PPA_DISTRIB_TARGET) + message(WARNING "Variable PPA_DEBIAN_VERSION not set! Building 'native' package!") + set(DEBIAN_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}") +else() + set(DEBIAN_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}~${DISTRI}1") +endif() + +message(STATUS "Debian version: ${DEBIAN_PACKAGE_VERSION}") + +set(DEBIAN_SOURCE_DIR ${CMAKE_BINARY_DIR}/Debian/${DISTRI}/${CPACK_DEBIAN_PACKAGE_NAME}_${DEBIAN_PACKAGE_VERSION}) + +############################################################################## +# debian/control + +set(debian_control ${DEBIAN_SOURCE_DIR}/debian/control) +list(APPEND CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS "cmake" "debhelper (>= 7.0.50)") +list(REMOVE_DUPLICATES CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS) +list(SORT CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS) +string(REPLACE ";" ", " build_depends "${CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS}") +string(REPLACE ";" ", " bin_depends "${CPACK_DEBIAN_PACKAGE_DEPENDS}") +file(WRITE ${debian_control} + "Source: ${CPACK_DEBIAN_PACKAGE_NAME}\n" + "Section: ${CPACK_DEBIAN_PACKAGE_SECTION}\n" + "Priority: ${CPACK_DEBIAN_PACKAGE_PRIORITY}\n" + "Maintainer: ${CPACK_DEBIAN_PACKAGE_MAINTAINER}\n" + "Build-Depends: ${build_depends}\n" + "Standards-Version: 3.9.7\n" + "Homepage: ${CPACK_DEBIAN_PACKAGE_HOMEPAGE}\n" + "\n" + "Package: ${CPACK_DEBIAN_PACKAGE_NAME}\n" + "Architecture: ${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}\n" + "Depends: ${bin_depends}, \${shlibs:Depends}, \${misc:Depends}\n" + "Description: ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}\n" + "${deb_long_description}" + ) + +file(APPEND ${debian_control} + "\n\n" + "Package: ${CPACK_DEBIAN_PACKAGE_NAME}-dbg\n" + "Priority: extra\n" + "Section: debug\n" + "Architecture: any\n" + "Depends: ${CPACK_DEBIAN_PACKAGE_NAME} (= \${binary:Version}), \${shlibs:Depends}, \${misc:Depends}\n" + "Description: ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}\n" + "${deb_long_description}" + "\n .\n" + " This is the debugging symbols for the ${CPACK_DEBIAN_PACKAGE_NAME} library" + ) + +foreach(COMPONENT ${CPACK_COMPONENTS_ALL}) + string(TOUPPER ${COMPONENT} UPPER_COMPONENT) + set(DEPENDS "${CPACK_DEBIAN_PACKAGE_NAME}") + foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS}) + set(DEPENDS "${DEPENDS}, ${CPACK_DEBIAN_PACKAGE_NAME}-${DEP}") + endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS}) + file(APPEND ${debian_control} "\n" + "Package: ${CPACK_DEBIAN_PACKAGE_NAME}-${COMPONENT}\n" + "Architecture: ${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}\n" + "Depends: ${DEPENDS}\n" + "Description: ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}" + ": ${CPACK_COMPONENT_${UPPER_COMPONENT}_DISPLAY_NAME}\n" + "${deb_long_description}" + "\n .\n" + " ${CPACK_COMPONENT_${UPPER_COMPONENT}_DESCRIPTION}\n" + ) +endforeach(COMPONENT ${CPACK_COMPONENTS_ALL}) + + + +############################################################################## +# debian/copyright +set(debian_copyright ${DEBIAN_SOURCE_DIR}/debian/copyright) +configure_file(${CPACK_RESOURCE_FILE_LICENSE} ${debian_copyright} COPYONLY) + +############################################################################## +# debian/rules +set(debian_rules ${DEBIAN_SOURCE_DIR}/debian/rules) + +file(WRITE ${debian_rules} + "#!/usr/bin/make -f\n" + "\nexport DH_VERBOSE=1" + "\n\n%:\n" + "\tdh $@ --buildsystem=cmake\n" + "\noverride_dh_auto_configure:\n" + "\tDESTDIR=\"$(CURDIR)/debian/${CPACK_DEBIAN_PACKAGE_NAME}\" dh_auto_configure -- -DCMAKE_BUILD_TYPE=RelWithDebInfo -DPACKAGE_TGZ=OFF ${DEB_CMAKE_ARGS}" + "\n\noverride_dh_auto_install:\n" + "\tdh_auto_install --destdir=\"$(CURDIR)/debian/${CPACK_DEBIAN_PACKAGE_NAME}\" --buildsystem=cmake" + "\n\noverride_dh_strip:\n" + "\tdh_strip --dbg-package=${CPACK_DEBIAN_PACKAGE_NAME}-dbg" +) + +execute_process(COMMAND chmod +x ${debian_rules}) + +############################################################################## +# debian/compat +file(WRITE ${DEBIAN_SOURCE_DIR}/debian/compat "7") + +############################################################################## +# debian/source/format +file(WRITE ${DEBIAN_SOURCE_DIR}/debian/source/format "3.0 (native)") + +############################################################################## + +# debian/changelog +set(debian_changelog ${DEBIAN_SOURCE_DIR}/debian/changelog) +if(NOT CPACK_DEBIAN_RESOURCE_FILE_CHANGELOG) + set(CPACK_DEBIAN_RESOURCE_FILE_CHANGELOG ${CMAKE_SOURCE_DIR}/debian/changelog) +endif() + +# TODO add support for git dch (git-buildpackage) +if(CHANGELOG_MESSAGE) + set(output_changelog_msg ${CHANGELOG_MESSAGE}) +else() + set(output_changelog_msg "* Package created with CMake") +endif(CHANGELOG_MESSAGE) +message(STATUS "Changelog message : \"${output_changelog_msg}\"") +if(EXISTS ${CPACK_DEBIAN_RESOURCE_FILE_CHANGELOG}) + configure_file(${CPACK_DEBIAN_RESOURCE_FILE_CHANGELOG} ${debian_changelog} COPYONLY) + + if(CPACK_DEBIAN_UPDATE_CHANGELOG) + file(READ ${debian_changelog} debian_changelog_content) + execute_process( + COMMAND date -R + OUTPUT_VARIABLE DATE_TIME + OUTPUT_STRIP_TRAILING_WHITESPACE) + file(WRITE ${debian_changelog} + "${CPACK_DEBIAN_PACKAGE_NAME} (${DEBIAN_PACKAGE_VERSION}) ${DISTRI}; urgency=low\n\n" + " ${output_changelog_msg}\n\n" + " -- ${CPACK_DEBIAN_PACKAGE_MAINTAINER} ${DATE_TIME}\n\n" + ) + file(APPEND ${debian_changelog} ${debian_changelog_content}) + endif() + +else() + execute_process( + COMMAND date -R + OUTPUT_VARIABLE DATE_TIME + OUTPUT_STRIP_TRAILING_WHITESPACE) + file(WRITE ${debian_changelog} + "${CPACK_DEBIAN_PACKAGE_NAME} (${DEBIAN_PACKAGE_VERSION}) ${DISTRI}; urgency=low\n\n" + " ${output_changelog_msg}\n\n" + " -- ${CPACK_DEBIAN_PACKAGE_MAINTAINER} ${DATE_TIME}\n" + ) + #configure_file(${debian_changelog} ${CPACK_DEBIAN_RESOURCE_FILE_CHANGELOG} COPYONLY) +endif() + + +########################################################################## +# Templates + +if (CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA) + foreach(CF ${CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA}) + get_filename_component(CF_NAME ${CF} NAME) + message("Writing debian/${CF_NAME}") + configure_file(${CF} ${DEBIAN_SOURCE_DIR}/debian/${CF_NAME} @ONLY) + endforeach() +endif(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA) + + +########################################################################## +# .orig.tar.gz +#execute_process(COMMAND date +%y%m%d +# OUTPUT_VARIABLE day_suffix +# OUTPUT_STRIP_TRAILING_WHITESPACE +# ) + +set(CPACK_SOURCE_IGNORE_FILES + ${CPACK_SOURCE_IGNORE_FILES} + "/build.*/" + "/Testing/" + "/test/" + "/tmp/" + "/packaging/" + "/debian/" + "/node_modules/" + "/\\\\.git.*" + "/\\\\.idea/" + "/\\\\.codelite/" + "*~$") + +#set(package_file_name "${CPACK_DEBIAN_PACKAGE_NAME}_${DEBIAN_PACKAGE_VERSION}") +set(package_file_name "${CPACK_DEBIAN_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}") + +file(WRITE "${CMAKE_BINARY_DIR}/Debian/${DISTRI}/cpack.cmake" + "set(CPACK_GENERATOR TGZ)\n" + "set(CPACK_PACKAGE_NAME \"${CPACK_DEBIAN_PACKAGE_NAME}\")\n" + "set(CPACK_PACKAGE_VERSION \"${CPACK_PACKAGE_VERSION}\")\n" + "set(CPACK_PACKAGE_FILE_NAME \"${package_file_name}.orig\")\n" + "set(CPACK_PACKAGE_DESCRIPTION \"${CPACK_PACKAGE_NAME} Source\")\n" + "set(CPACK_IGNORE_FILES \"${CPACK_SOURCE_IGNORE_FILES}\")\n" + "set(CPACK_INSTALLED_DIRECTORIES \"${CPACK_SOURCE_INSTALLED_DIRECTORIES}\")\n" + "set(CPACK_INSTALL_SCRIPT \"${CPACK_SOURCE_INSTALL_SCRIPT}\")\n" + "set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF)\n" + ) + +set(orig_file "${CMAKE_BINARY_DIR}/Debian/${DISTRI}/${package_file_name}.orig.tar.gz") + +add_custom_command(OUTPUT ${orig_file} + COMMAND cpack --config ${CMAKE_BINARY_DIR}/Debian/${DISTRI}/cpack.cmake + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/Debian/${DISTRI} + ) + +add_custom_command(OUTPUT ${DEBIAN_SOURCE_DIR}/CMakeLists.txt + COMMAND tar zxf ${orig_file} + WORKING_DIRECTORY ${DEBIAN_SOURCE_DIR} + DEPENDS ${orig_file} + ) + +############################################################################## +# debuild -S +set(DEB_SOURCE_CHANGES + ${CPACK_DEBIAN_PACKAGE_NAME}_${DEBIAN_PACKAGE_VERSION}_source.changes + ) + +add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/Debian/${DISTRI}/${DEB_SOURCE_CHANGES} + COMMAND ${DEBUILD_EXECUTABLE} --no-tgz-check -S + WORKING_DIRECTORY ${DEBIAN_SOURCE_DIR} + ) +add_custom_target(debuild_${DISTRI} ALL + DEPENDS ${DEBIAN_SOURCE_DIR}/CMakeLists.txt + ${CMAKE_BINARY_DIR}/Debian/${DISTRI}/${DEB_SOURCE_CHANGES} + ) +############################################################################## +# dput ppa:your-lp-id/ppa +message(STATUS "Upload PPA is ${UPLOAD_PPA}") +if(UPLOAD_PPA) + if (EXISTS ${DPUT_CONFIG_IN}) + set(DPUT_DIST ${DISTRI}) + configure_file( + ${DPUT_CONFIG_IN} + ${CMAKE_BINARY_DIR}/Debian/${DISTRI}/dput.cf + @ONLY + ) + add_custom_target(dput_${DISTRI} ALL + COMMAND ${DPUT_EXECUTABLE} -c ${CMAKE_BINARY_DIR}/Debian/${DISTRI}/dput.cf ${DPUT_HOST} ${DEB_SOURCE_CHANGES} + DEPENDS debuild_${DISTRI} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/Debian/${DISTRI} + ) + else() + add_custom_target(dput_${DISTRI} ALL + COMMAND ${DPUT_EXECUTABLE} ${DPUT_HOST} ${DEB_SOURCE_CHANGES} + DEPENDS debuild_${DISTRI} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/Debian/${DISTRI} + ) + endif() +endif() +endforeach(DISTRI) diff --git a/cmake/Modules/Win32LIBTools.cmake b/cmake/Modules/Win32LIBTools.cmake new file mode 100644 index 0000000..24448de --- /dev/null +++ b/cmake/Modules/Win32LIBTools.cmake @@ -0,0 +1,53 @@ +if (NOT CMAKE_HOST_UNIX OR NOT WIN32) + return() +endif() + +find_program(WINTOOLS_WINE_EXEC wine) +if (NOT WINTOOLS_WINE_EXEC) + message("Wine executable not found! Failed to initiate wintools staff.") + return() +endif() + +find_program(WINTOOLS_WGET_EXEC wget) +if (NOT WINTOOLS_WGET_EXEC) + message("Wget executable not found! Failed to initiate wintools staff.") + return() +endif() + +set(WINTOOLS_DIR ${CMAKE_BINARY_DIR}/WINTOOLS) +set(WINTOOLS_DL_ROOT "http://softmotions.com/windev") + +if (NOT EXISTS ${WINTOOLS_DIR}) + file(MAKE_DIRECTORY ${WINTOOLS_DIR}) +endif() + +set(WINTOOLS_EXECS) +foreach (WINTOOLS_EXEC link.exe lib.exe mspdb100.dll) + if (NOT EXISTS ${WINTOOLS_DIR}/${WINTOOLS_EXEC}) + add_custom_command(OUTPUT ${WINTOOLS_DIR}/${WINTOOLS_EXEC} + COMMAND ${WINTOOLS_WGET_EXEC} ${WINTOOLS_DL_ROOT}/${WINTOOLS_EXEC} -nv -O${WINTOOLS_DIR}/${WINTOOLS_EXEC} + WORKING_DIRECTORY ${WINTOOLS_DIR}) + list(APPEND WINTOOLS_EXECS ${WINTOOLS_DIR}/${WINTOOLS_EXEC}) + endif() +endforeach(WINTOOLS_EXEC) + +add_custom_target(wintools_init + DEPENDS ${WINTOOLS_EXECS}) + +if (CMAKE_SIZEOF_VOID_P MATCHES 8) + set(WINTOOLS_LIB_MACHINE "X64") +else() + set(WINTOOLS_LIB_MACHINE "X86") +endif() +message("${PROJECT_NAME} WINTOOLS_LIB_MACHINE: ${WINTOOLS_LIB_MACHINE}") + +macro(add_w32_importlib tgt libname wdir) + add_custom_command( + TARGET ${tgt} + POST_BUILD + COMMAND ${WINTOOLS_WINE_EXEC} ${WINTOOLS_DIR}/lib.exe /def:${libname}.def /machine:${WINTOOLS_LIB_MACHINE} + WORKING_DIRECTORY ${wdir} + ) +endmacro(add_w32_importlib) + + diff --git a/copy_iowow_header.py b/copy_iowow_header.py new file mode 100755 index 0000000..ca0c495 --- /dev/null +++ b/copy_iowow_header.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import sys +import argparse +import os +import shutil + +def copy_file(src_dir, dst_dir): + if not os.path.exists(dst_dir): + os.makedirs(dst_dir) + headers = [ + 'basedefs.h', + 'fs/iwdlsnr.h', + 'fs/iwexfile.h', + 'fs/iwfile.h', + 'fs/iwfsmfile.h', + 'iowow.h', + #'kv/iwal.h', + 'kv/iwkv.h', + #'kv/iwkv_internal.h', + #'kv/tests/iwkv_tests.h', + 'log/iwlog.h', + 'platform/iwp.h', + #'platform/win32/mman/mman.h', + 'rdb/iwrdb.h', + 'tmpl/iwcfg.h', + 'utils/iwarr.h', + 'utils/iwbits.h', + 'utils/iwconv.h', + 'utils/iwhmap.h', + 'utils/iwpool.h', + 'utils/iwsha2.h', + 'utils/iwstree.h', + 'utils/iwstw.h', + 'utils/iwth.h', + 'utils/iwutils.h', + 'utils/iwuuid.h', + 'utils/iwxstr.h', + #'utils/kbtree.h', + #'utils/khash.h', + #'utils/ksort.h', + #'utils/mt19937ar.h', + 'utils/murmur3.h', + #'utils/pthread_spin_lock_shim.h' + ] + for header in headers: + src_file = os.path.join(src_dir, header) + file_tags = header.split('/') + if (len(file_tags) == 1): + des_file = os.path.join(dst_dir, header) + else: + des_file = os.path.join(dst_dir, file_tags[len(file_tags) - 1]) + shutil.copy2(src_file, des_file) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--src-dir', help='source path of iowow header file', required=True) + parser.add_argument('--dst-dir', help='destion path of iowow header file', required=True) + args = parser.parse_args() + print('copy from %s to %s', args.src_dir, args.dst_dir) + copy_file(args.src_dir, args.dst_dir) + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..99b87f4 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,22 @@ +FROM alpine:3.9 + +MAINTAINER Anton Adamansky + +RUN apk add --no-cache cmake gcc binutils libc-dev git ninja + +RUN git clone https://github.com/Softmotions/ejdb.git && \ + mkdir -p ejdb/build + +WORKDIR ejdb/build + +RUN cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/opt/ejdb2 && \ + ninja install && \ + mkdir -p /ejdb2_data + +VOLUME ["/ejdb2_data"] + +EXPOSE 9191 + +WORKDIR /ejdb2_data + +ENTRYPOINT ["/opt/ejdb2/bin/jbs", "-b", "0.0.0.0"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..85962bb --- /dev/null +++ b/docker/README.md @@ -0,0 +1,15 @@ +# Docker support + +If you have [Docker]("https://www.docker.com/") installed, you can build a Docker image and run it in a container + +``` +cd docker +docker build -t ejdb2 . +docker run -d -p 9191:9191 --name myEJDB ejdb2 --access myAccessKey +``` + +or get an image of `ejdb2` directly from the Docker Hub + +``` +docker run -d -p 9191:9191 --name myEJDB softmotions/ejdb2 --access myAccessKey +``` \ No newline at end of file diff --git a/docker/testbed/Dockerfile b/docker/testbed/Dockerfile new file mode 100644 index 0000000..29ba3a0 --- /dev/null +++ b/docker/testbed/Dockerfile @@ -0,0 +1,28 @@ +# EJDB2 testbed docker image + +FROM ubuntu:xenial + +LABEL maintainer="adamansky@gmail.com" + +RUN mkdir /setup + +WORKDIR /setup +COPY ./*.sh ./ +RUN ./setup.sh + +VOLUME /home/worker/.jenkins +VOLUME /home/worker/agent + +USER root +COPY jenkins-agent /usr/local/bin/jenkins-agent +RUN chmod +x /usr/local/bin/jenkins-agent &&\ + ln -s /usr/local/bin/jenkins-agent /usr/local/bin/jenkins-slave + +USER worker +WORKDIR /home/worker + +ENTRYPOINT ["/usr/local/bin/jenkins-agent"] + + + + diff --git a/docker/testbed/jenkins-agent b/docker/testbed/jenkins-agent new file mode 100755 index 0000000..a809de5 --- /dev/null +++ b/docker/testbed/jenkins-agent @@ -0,0 +1,117 @@ +#!/usr/bin/env bash + +# The MIT License +# +# Copyright (c) 2015-2020, CloudBees, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# Usage jenkins-agent.sh [options] -url http://jenkins [SECRET] [AGENT_NAME] +# Optional environment variables : +# * JENKINS_TUNNEL : HOST:PORT for a tunnel to route TCP traffic to jenkins host, when jenkins can't be directly accessed over network +# * JENKINS_URL : alternate jenkins URL +# * JENKINS_SECRET : agent secret, if not set as an argument +# * JENKINS_AGENT_NAME : agent name, if not set as an argument +# * JENKINS_AGENT_WORKDIR : agent work directory, if not set by optional parameter -workDir +# * JENKINS_WEB_SOCKET: true if the connection should be made via WebSocket rather than TCP +# * JENKINS_DIRECT_CONNECTION: Connect directly to this TCP agent port, skipping the HTTP(S) connection parameter download. +# Value: ":" +# * JENKINS_INSTANCE_IDENTITY: The base64 encoded InstanceIdentity byte array of the Jenkins master. When this is set, +# the agent skips connecting to an HTTP(S) port for connection info. +# * JENKINS_PROTOCOLS: Specify the remoting protocols to attempt when instanceIdentity is provided. + +if [ $# -eq 1 ]; then + + # if `docker run` only has one arguments, we assume user is running alternate command like `bash` to inspect the image + exec "$@" + +else + + # if -tunnel is not provided, try env vars + case "$@" in + *"-tunnel "*) ;; + *) + if [ ! -z "$JENKINS_TUNNEL" ]; then + TUNNEL="-tunnel $JENKINS_TUNNEL" + fi ;; + esac + + # if -workDir is not provided, try env vars + if [ ! -z "$JENKINS_AGENT_WORKDIR" ]; then + case "$@" in + *"-workDir"*) echo "Warning: Work directory is defined twice in command-line arguments and the environment variable" ;; + *) + WORKDIR="-workDir $JENKINS_AGENT_WORKDIR" ;; + esac + fi + + if [ -n "$JENKINS_URL" ]; then + URL="-url $JENKINS_URL" + fi + + if [ -n "$JENKINS_NAME" ]; then + JENKINS_AGENT_NAME="$JENKINS_NAME" + fi + + if [ "$JENKINS_WEB_SOCKET" = true ]; then + WEB_SOCKET=-webSocket + fi + + if [ -n "$JENKINS_PROTOCOLS" ]; then + PROTOCOLS="-protocols $JENKINS_PROTOCOLS" + fi + + if [ -n "$JENKINS_DIRECT_CONNECTION" ]; then + DIRECT="-direct $JENKINS_DIRECT_CONNECTION" + fi + + if [ -n "$JENKINS_INSTANCE_IDENTITY" ]; then + INSTANCE_IDENTITY="-instanceIdentity $JENKINS_INSTANCE_IDENTITY" + fi + + # if java home is defined, use it + JAVA_BIN="java" + if [ "$JAVA_HOME" ]; then + JAVA_BIN="$JAVA_HOME/bin/java" + fi + + # if both required options are defined, do not pass the parameters + OPT_JENKINS_SECRET="" + if [ -n "$JENKINS_SECRET" ]; then + case "$@" in + *"${JENKINS_SECRET}"*) echo "Warning: SECRET is defined twice in command-line arguments and the environment variable" ;; + *) + OPT_JENKINS_SECRET="${JENKINS_SECRET}" ;; + esac + fi + + OPT_JENKINS_AGENT_NAME="" + if [ -n "$JENKINS_AGENT_NAME" ]; then + case "$@" in + *"${JENKINS_AGENT_NAME}"*) echo "Warning: AGENT_NAME is defined twice in command-line arguments and the environment variable" ;; + *) + OPT_JENKINS_AGENT_NAME="${JENKINS_AGENT_NAME}" ;; + esac + fi + + #TODO: Handle the case when the command-line and Environment variable contain different values. + #It is fine it blows up for now since it should lead to an error anyway. + + exec $JAVA_BIN $JAVA_OPTS -cp /usr/share/jenkins/agent.jar hudson.remoting.jnlp.Main -headless $TUNNEL $URL $WORKDIR $WEB_SOCKET $DIRECT $PROTOCOLS $INSTANCE_IDENTITY $OPT_JENKINS_SECRET $OPT_JENKINS_AGENT_NAME "$@" +fi diff --git a/docker/testbed/llvm-update-alternatives.sh b/docker/testbed/llvm-update-alternatives.sh new file mode 100755 index 0000000..9ae4774 --- /dev/null +++ b/docker/testbed/llvm-update-alternatives.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash + +set -e +set -x + +function register_clang_version { + local version=$1 + local priority=$2 + + update-alternatives \ + --install /usr/bin/llvm-config llvm-config /usr/bin/llvm-config-${version} ${priority} \ + --slave /usr/bin/llvm-addr2line llvm-addr2line /usr/bin/llvm-addr2line-${version} \ + --slave /usr/bin/llvm-ar llvm-ar /usr/bin/llvm-ar-${version} \ + --slave /usr/bin/llvm-as llvm-as /usr/bin/llvm-as-${version} \ + --slave /usr/bin/llvm-bcanalyzer llvm-bcanalyzer /usr/bin/llvm-bcanalyzer-${version} \ + --slave /usr/bin/llvm-cov llvm-cov /usr/bin/llvm-cov-${version} \ + --slave /usr/bin/llvm-diff llvm-diff /usr/bin/llvm-diff-${version} \ + --slave /usr/bin/llvm-dis llvm-dis /usr/bin/llvm-dis-${version} \ + --slave /usr/bin/llvm-dwarfdump llvm-dwarfdump /usr/bin/llvm-dwarfdump-${version} \ + --slave /usr/bin/llvm-exegesis llvm-exegesis /usr/bin/llvm-exegesis-${version} \ + --slave /usr/bin/llvm-extract llvm-extract /usr/bin/llvm-extract-${version} \ + --slave /usr/bin/llvm-link llvm-link /usr/bin/llvm-link-${version} \ + --slave /usr/bin/llvm-lipo llvm-lipo /usr/bin/llvm-lipo-${version} \ + --slave /usr/bin/llvm-lto llvm-lto /usr/bin/llvm-lto-${version} \ + --slave /usr/bin/llvm-lto2 llvm-lto2 /usr/bin/llvm-lto2-${version} \ + --slave /usr/bin/llvm-mc llvm-mc /usr/bin/llvm-mc-${version} \ + --slave /usr/bin/llvm-mcmarkup llvm-mcmarkup /usr/bin/llvm-mcmarkup-${version} \ + --slave /usr/bin/llvm-nm llvm-nm /usr/bin/llvm-nm-${version} \ + --slave /usr/bin/llvm-objcopy llvm-objcopy /usr/bin/llvm-objcopy-${version} \ + --slave /usr/bin/llvm-objdump llvm-objdump /usr/bin/llvm-objdump-${version} \ + --slave /usr/bin/llvm-opt-report llvm-opt-report /usr/bin/llvm-opt-report-${version} \ + --slave /usr/bin/llvm-pdbutil llvm-pdbutil /usr/bin/llvm-pdbutil-${version} \ + --slave /usr/bin/llvm-profdata llvm-profdata /usr/bin/llvm-profdata-${version} \ + --slave /usr/bin/llvm-ranlib llvm-ranlib /usr/bin/llvm-ranlib-${version} \ + --slave /usr/bin/llvm-rc llvm-rc /usr/bin/llvm-rc-${version} \ + --slave /usr/bin/llvm-readelf llvm-readelf /usr/bin/llvm-readelf-${version} \ + --slave /usr/bin/llvm-readobj llvm-readobj /usr/bin/llvm-readobj-${version} \ + --slave /usr/bin/llvm-rtdyld llvm-rtdyld /usr/bin/llvm-rtdyld-${version} \ + --slave /usr/bin/llvm-size llvm-size /usr/bin/llvm-size-${version} \ + --slave /usr/bin/llvm-split llvm-split /usr/bin/llvm-split-${version} \ + --slave /usr/bin/llvm-stress llvm-stress /usr/bin/llvm-stress-${version} \ + --slave /usr/bin/llvm-strip llvm-strip /usr/bin/llvm-strip-${version} \ + --slave /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-${version} \ + --slave /usr/bin/llvm-tblgen llvm-tblgen /usr/bin/llvm-tblgen-${version} \ + --slave /usr/bin/llvm-undname llvm-undname /usr/bin/llvm-undname-${version} + + update-alternatives \ + --install /usr/bin/clang clang /usr/bin/clang-${version} ${priority} \ + --slave /usr/bin/asan_symbolize asan_symbolize /usr/bin/asan_symbolize-${version} \ + --slave /usr/bin/c-index-test c-index-test /usr/bin/c-index-test-${version} \ + --slave /usr/bin/clang++ clang++ /usr/bin/clang++-${version} \ + --slave /usr/bin/clang-check clang-check /usr/bin/clang-check-${version} \ + --slave /usr/bin/clang-cl clang-cl /usr/bin/clang-cl-${version} \ + --slave /usr/bin/clang-cpp clang-cpp /usr/bin/clang-cpp-${version} \ + --slave /usr/bin/clang-format clang-format /usr/bin/clang-format-${version} \ + --slave /usr/bin/clang-format-diff clang-format-diff /usr/bin/clang-format-diff-${version} \ + --slave /usr/bin/clang-import-test clang-import-test /usr/bin/clang-import-test-${version} \ + --slave /usr/bin/clang-include-fixer clang-include-fixer /usr/bin/clang-include-fixer-${version} \ + --slave /usr/bin/clang-offload-bundler clang-offload-bundler /usr/bin/clang-offload-bundler-${version} \ + --slave /usr/bin/clang-query clang-query /usr/bin/clang-query-${version} \ + --slave /usr/bin/clang-rename clang-rename /usr/bin/clang-rename-${version} \ + --slave /usr/bin/clang-reorder-fields clang-reorder-fields /usr/bin/clang-reorder-fields-${version} \ + --slave /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-${version} \ + --slave /usr/bin/clangd clangd /usr/bin/clangd-${version} \ + --slave /usr/bin/lld lld /usr/bin/lld-${version} \ + --slave /usr/bin/lld-link lld-link /usr/bin/lld-link-${version} \ + --slave /usr/bin/lldb lldb /usr/bin/lldb-${version} \ + --slave /usr/bin/lldb-argdumper lldb-argdumper /usr/bin/lldb-argdumper-${version} \ + --slave /usr/bin/lldb-instr lldb-instr /usr/bin/lldb-instr-${version} \ + --slave /usr/bin/lldb-server lldb-server /usr/bin/lldb-server-${version} \ + --slave /usr/bin/lldb-vscode lldb-vscode /usr/bin/lldb-vscode-${version} \ + --slave /usr/bin/lli lli /usr/bin/lli-${version} \ + --slave /usr/bin/lli-child-target lli-child-target /usr/bin/lli-child-target-${version} +} + +register_clang_version $1 $2 diff --git a/docker/testbed/llvm.sh b/docker/testbed/llvm.sh new file mode 100755 index 0000000..1fb8c12 --- /dev/null +++ b/docker/testbed/llvm.sh @@ -0,0 +1,62 @@ +#!/bin/bash +################################################################################ +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +################################################################################ +# +# This script will install the llvm toolchain on the different +# Debian and Ubuntu versions + +set -eux + +# read optional command line argument +LLVM_VERSION=10 +if [ "$#" -eq 1 ]; then + LLVM_VERSION=$1 +fi + +DISTRO=$(lsb_release -is) +VERSION=$(lsb_release -sr) +DIST_VERSION="${DISTRO}_${VERSION}" + +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root!" + exit 1 +fi + +declare -A LLVM_VERSION_PATTERNS +LLVM_VERSION_PATTERNS[9]="-9" +LLVM_VERSION_PATTERNS[10]="-10" +LLVM_VERSION_PATTERNS[11]="-11" + +if [ ! ${LLVM_VERSION_PATTERNS[$LLVM_VERSION]+_} ]; then + echo "This script does not support LLVM version $LLVM_VERSION" + exit 3 +fi + +LLVM_VERSION_STRING=${LLVM_VERSION_PATTERNS[$LLVM_VERSION]} + +# find the right repository name for the distro and version +case "$DIST_VERSION" in + Debian_9* ) REPO_NAME="deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch$LLVM_VERSION_STRING main" ;; + Debian_10* ) REPO_NAME="deb http://apt.llvm.org/buster/ llvm-toolchain-buster$LLVM_VERSION_STRING main" ;; + Debian_unstable ) REPO_NAME="deb http://apt.llvm.org/unstable/ llvm-toolchain$LLVM_VERSION_STRING main" ;; + Debian_testing ) REPO_NAME="deb http://apt.llvm.org/unstable/ llvm-toolchain$LLVM_VERSION_STRING main" ;; + Ubuntu_16.04 ) REPO_NAME="deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial$LLVM_VERSION_STRING main" ;; + Ubuntu_18.04 ) REPO_NAME="deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic$LLVM_VERSION_STRING main" ;; + Ubuntu_18.10 ) REPO_NAME="deb http://apt.llvm.org/cosmic/ llvm-toolchain-cosmic$LLVM_VERSION_STRING main" ;; + Ubuntu_19.04 ) REPO_NAME="deb http://apt.llvm.org/disco/ llvm-toolchain-disco$LLVM_VERSION_STRING main" ;; + Ubuntu_19.10 ) REPO_NAME="deb http://apt.llvm.org/eoan/ llvm-toolchain-eoan$LLVM_VERSION_STRING main" ;; + Ubuntu_20.04 ) REPO_NAME="deb http://apt.llvm.org/focal/ llvm-toolchain-focal$LLVM_VERSION_STRING main" ;; + * ) + echo "Distribution '$DISTRO' in version '$VERSION' is not supported by this script (${DIST_VERSION})." + exit 2 +esac + + +# install everything +wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - +add-apt-repository "${REPO_NAME}" +apt-get update +apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION diff --git a/docker/testbed/setup.sh b/docker/testbed/setup.sh new file mode 100755 index 0000000..c2d40a4 --- /dev/null +++ b/docker/testbed/setup.sh @@ -0,0 +1,202 @@ +#!/bin/bash + +set -e +set -x + +echo "Setup docker host" + +SCRIPTPATH="$( + cd "$(dirname "$0")" + pwd -P +)" +cd $SCRIPTPATH + +JENKINS_AGENT_VERSION=4.6 + +dpkg --add-architecture i386 +apt-get update +apt-get install -y apt-utils software-properties-common apt-transport-https sudo curl wget zip unzip +apt-get update +apt-get dist-upgrade -y + +wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null +wget -qO- https://deb.nodesource.com/setup_12.x | bash - +wget -qO- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - +wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - + +apt-add-repository -y 'deb https://apt.kitware.com/ubuntu/ xenial main' +echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list +wget -qO- https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_unstable.list >/etc/apt/sources.list.d/dart_unstable.list + +./llvm.sh 10 +./llvm-update-alternatives.sh 10 1000 + +apt-get update + +echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | debconf-set-selections + +apt-get install -y \ + autopoint \ + binutils \ + bison \ + build-essential \ + ca-certificates \ + cmake \ + dart \ + debhelper \ + debianutils \ + devscripts \ + flex \ + g++ \ + gcc \ + git \ + gnupg \ + gperf \ + intltool \ + lib32z1 \ + libc6-dbg \ + libcunit1-dev \ + libcurl4-openssl-dev \ + libgdk-pixbuf2.0-0 \ + libgdk-pixbuf2.0-dev \ + libtool \ + libtool-bin \ + lzip \ + make \ + mc \ + nano \ + ninja \ + nodejs \ + openjdk-8-jdk-headless \ + p7zip-full \ + ruby \ + scons \ + wine \ + yarn + +echo 'JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64' >> /etc/environment + +mkdir -p /opt +( + cd /opt + wget -qO swift.tar.gz https://swift.org/builds/swift-5.2.3-release/ubuntu1604/swift-5.2.3-RELEASE/swift-5.2.3-RELEASE-ubuntu16.04.tar.gz + tar -xf ./swift.tar.gz + rm -f ./swift.tar.gz +) + +useradd -d /home/worker -m -s /bin/bash worker + +sudo -iu worker /bin/bash -i <<"EOF" +set -e +set -x + +echo 'export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64' >> ~/.profile +echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.profile +curl -s "https://get.sdkman.io" | /bin/bash +EOF + +sudo -iu worker /bin/bash -i <<"EOF" +set -e +set -x + +sdk install gradle 5.4.1 +sdk install maven + +echo 'PATH=/usr/lib/dart/bin:$PATH' >> ~/.profile +echo 'PATH=~/.sdkman/candidates/maven/current/bin:$PATH' >> ~/.profile +echo 'PATH=~/.sdkman/candidates/gradle/current/bin:$PATH' >> ~/.profile +echo 'PATH=$PATH:/opt/swift-5.2.3-RELEASE-ubuntu16.04/usr/bin' >> ~/.profile +echo 'export PATH=$PATH:~/.yarn/bin' >> ~/.profile +EOF + +sudo -iu worker /bin/bash -i <<"EOF" +set -e +set -x + +wget -O commandlinetools.zip https://dl.google.com/android/repository/commandlinetools-linux-6200805_latest.zip +unzip commandlinetools.zip -d ./Android +rm commandlinetools.zip + +echo 'export ANDROID_NDK_VERSION=21.1.6352462' >> ~/.profile +echo 'export ANDROID_HOME=~/Android' >> ~/.profile +echo 'export ANDROID_SDK_ROOT=${ANDROID_HOME}' >> ~/.profile +echo 'PATH=$PATH:$ANDROID_HOME/tools' >> ~/.profile +echo 'PATH=$PATH:$ANDROID_HOME/tools/bin' >> ~/.profile +echo 'PATH=$PATH:$ANDROID_HOME/platform-tools' >> ~/.profile +echo 'PATH=~/flutter/bin:$PATH' >> ~/.profile +echo 'export PATH' >> ~/.profile +echo 'export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/$ANDROID_NDK_VERSION' >> ~/.profile +EOF + +sudo -iu worker /bin/bash -i <<"EOF" +set -e +set -x + +yes | sdkmanager --sdk_root=${ANDROID_HOME} --licenses +sdkmanager --sdk_root=${ANDROID_HOME} \ + --install \ + "platform-tools" \ + "platforms;android-28" \ + "platforms;android-29" \ + "ndk;${ANDROID_NDK_VERSION}" \ + "build-tools;28.0.3" \ + "build-tools;29.0.3" + +sdkmanager --sdk_root=${ANDROID_HOME} tools +yes | sdkmanager --sdk_root=${ANDROID_HOME} --licenses +EOF + +sudo -iu worker /bin/bash -i <<"EOF" +set -e +set -x + +git clone https://github.com/flutter/flutter.git -b stable +flutter config --android-sdk $ANDROID_HOME +flutter doctor --android-licenses +flutter precache +EOF + +sudo -iu worker /bin/bash -i <<"EOF" +set -e +set -x + +mkdir -p ~/tmp && cd ~/tmp +wget https://sourceware.org/pub/valgrind/valgrind-3.15.0.tar.bz2 +tar -xf ./valgrind-3.15.0.tar.bz2 +cd ./valgrind-3.15.0 +./configure --prefix=/home/worker/.local +make && make install +cd ~/ && rm -rf ./tmp/* +EOF + +curl --create-dirs -fsSLo /usr/share/jenkins/agent.jar \ + https://repo.jenkins-ci.org/public/org/jenkins-ci/main/remoting/${JENKINS_AGENT_VERSION}/remoting-${JENKINS_AGENT_VERSION}.jar +chmod 755 /usr/share/jenkins +chmod 644 /usr/share/jenkins/agent.jar +ln -sf /usr/share/jenkins/agent.jar /usr/share/jenkins/slave.jar + +sudo -iu worker /bin/bash -i <<"EOF" +set -e +set -x + +git clone https://github.com/mxe/mxe.git +cd ./mxe +echo 'JOBS := 1' > ./settings.mk +echo 'MXE_TARGETS := x86_64-w64-mingw32.static' >> ./settings.mk +echo 'LOCAL_PKG_LIST := cunit libiberty' >> ./settings.mk +echo '.DEFAULT local-pkg-list:' >> ./settings.mk +echo 'local-pkg-list: $(LOCAL_PKG_LIST)' >> ./settings.mk + +make +echo 'export MXE_HOME=~/mxe' >> ~/.profile +EOF + +sudo -iu worker /bin/bash -i <<"EOF" +set -e +set -x +mkdir -p ~/.jenkins +mkdir -p ~/agent +cat ~/.profile +echo $PATH +EOF + diff --git a/installer/CMakeLists.txt b/installer/CMakeLists.txt new file mode 100644 index 0000000..03d1629 --- /dev/null +++ b/installer/CMakeLists.txt @@ -0,0 +1,5 @@ +if (BUILD_JNI_BINDING) + add_subdirectory(jni) +else() + add_subdirectory(core) +endif() \ No newline at end of file diff --git a/installer/core/CMakeLists.txt b/installer/core/CMakeLists.txt new file mode 100644 index 0000000..867bd0c --- /dev/null +++ b/installer/core/CMakeLists.txt @@ -0,0 +1,62 @@ +set(CPACK_PACKAGE_NAME "${CMAKE_PROJECT_NAME}") +set(CPACK_PACKAGE_CONTACT "${PROJECT_MAINTAINER}") +set(CPACK_PACKAGE_VERSION ${ejdb2_VERSION}) +set(CPACK_PACKAGE_VERSION_MAJOR ${ejdb2_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${ejdb2_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${ejdb2_VERSION_PATCH}) +set(CPACK_PACKAGE_VENDOR ${PROJECT_VENDOR}) +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PROJECT_DESCRIPTION_SUMMARY}) +set(CPACK_PACKAGE_DESCRIPTION ${PROJECT_DESCRIPTION}) +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") +set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md") +set(CPACK_RESOURCE_FILE_WELCOME "${CMAKE_SOURCE_DIR}/README.md") +set(CPACK_PACKAGE_FILE_NAME + "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_BUILD_TYPE}-${CMAKE_SYSTEM_NAME}-${PROJECT_ARCH}") +if (CMAKE_BUILD_TYPE STREQUAL "Release") + set(CPACK_STRIP_FILES ON) +endif () + +set(DEB_CMAKE_ARGS "-DBUILD_SHARED_LIBS=ON") + +if (PACKAGE_DEB) + execute_process( + COMMAND /usr/bin/dpkg --print-architecture + OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE + RESULT_VARIABLE EXECUTE_RESULT + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + if (EXECUTE_RESULT) + message(FATAL_ERROR "dpkg not found: No package generation.") + endif () + set(CPACK_DEBIAN_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION}) + set(CPACK_DEBIAN_PACKAGE_HOMEPAGE ${PROJECT_WEBSITE}) + set(CPACK_DEBIAN_PACKAGE_SECTION libs) + set(CPACK_DEBIAN_PACKAGE_PRIORITY optional) + #set(CPACK_DEBIAN_PACKAGE_DEPENDS zlib1g) + set(CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS pkg-config git devscripts dh-make) + if (NOT PPA_DEBIAN_VERSION) + set(PPA_DEBIAN_VERSION ppa1) + endif () + if (PROJECT_PPA) + set(DPUT_HOST ${PROJECT_PPA}) + endif () + #set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "any") + set(CPACK_DEBIAN_RESOURCE_FILE_CHANGELOG ${CMAKE_SOURCE_DIR}/Changelog) + set(CPACK_DEBIAN_UPDATE_CHANGELOG ON) +endif (PACKAGE_DEB) + +if (PACKAGE_TGZ) + set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF) +endif (PACKAGE_TGZ) + +include(CPack) + +if (ENABLE_PPA) + set(CPACK_SOURCE_INSTALL_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/../fetch_libs.cmake") + if (NOT PROJECT_PPA_DISTRIB_TARGET) + set(PROJECT_PPA_DISTRIB_TARGET focal) + endif () + set(DPUT_CONFIG_IN ${CMAKE_CURRENT_SOURCE_DIR}/../debian/dput.cf.in) + include(UploadPPA) +endif () diff --git a/installer/debian/dput.cf.in b/installer/debian/dput.cf.in new file mode 100644 index 0000000..c78437b --- /dev/null +++ b/installer/debian/dput.cf.in @@ -0,0 +1,5 @@ +[ppa] +fqdn = ppa.launchpad.net +method = sftp +login = @PROJECT_PPA_USER@ +incoming = ~%(ppa)s/ubuntu/@DPUT_DIST@ diff --git a/installer/fetch_libs.cmake b/installer/fetch_libs.cmake new file mode 100644 index 0000000..6f563ab --- /dev/null +++ b/installer/fetch_libs.cmake @@ -0,0 +1,5 @@ +message("DOWNLOAD https://github.com/Softmotions/facil.io/archive/master.zip") +file(DOWNLOAD https://github.com/Softmotions/facil.io/archive/master.zip ${CMAKE_CURRENT_BINARY_DIR}/facil.zip) + +message("DOWNLOAD https://github.com/Softmotions/iowow/archive/master.zip") +file(DOWNLOAD https://github.com/Softmotions/iowow/archive/master.zip ${CMAKE_CURRENT_BINARY_DIR}/iowow.zip) diff --git a/installer/jni/CMakeLists.txt b/installer/jni/CMakeLists.txt new file mode 100644 index 0000000..97fcb85 --- /dev/null +++ b/installer/jni/CMakeLists.txt @@ -0,0 +1,73 @@ +file(READ ${CMAKE_SOURCE_DIR}/src/bindings/ejdb2_jni/version.txt + EJDB2_JNI_VERSION) +if(NOT EJDB2_JNI_VERSION) + message( + FATAL_ERROR + "${CMAKE_SOURCE_DIR}/src/bindings/ejdb2_jni/version.txt is not exists") +endif() +set(EJDB2_JNI_VERSION "${PROJECT_VERSION}.${EJDB2_JNI_VERSION}") + +set(CPACK_PACKAGE_NAME "${CMAKE_PROJECT_NAME}-java") +set(CPACK_PACKAGE_CONTACT "${PROJECT_MAINTAINER}") +set(CPACK_PACKAGE_VERSION ${EJDB2_JNI_VERSION}) +set(CPACK_PACKAGE_VERSION_MAJOR ${ejdb2_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${ejdb2_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${ejdb2_VERSION_PATCH}) +set(CPACK_PACKAGE_VENDOR ${PROJECT_VENDOR}) +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY + "${PROJECT_DESCRIPTION_SUMMARY} Java binding.") +set(CPACK_PACKAGE_DESCRIPTION "${PROJECT_DESCRIPTION} Java binding.") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") +set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md") +set(CPACK_RESOURCE_FILE_WELCOME "${CMAKE_SOURCE_DIR}/README.md") +set(CPACK_PACKAGE_FILE_NAME + "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_BUILD_TYPE}-${CMAKE_SYSTEM_NAME}-${PROJECT_ARCH}" +) +if(CMAKE_BUILD_TYPE STREQUAL "Release") + set(CPACK_STRIP_FILES ON) +endif() + +set(DEB_CMAKE_ARGS "-DBUILD_JNI_BINDING=ON") + +if(PACKAGE_DEB) + execute_process( + COMMAND /usr/bin/dpkg --print-architecture + OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE + RESULT_VARIABLE EXECUTE_RESULT + OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) + if(EXECUTE_RESULT) + message(FATAL_ERROR "dpkg not found: No package generation.") + endif() + set(CPACK_DEBIAN_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION}) + set(CPACK_DEBIAN_PACKAGE_HOMEPAGE ${PROJECT_WEBSITE}) + set(CPACK_DEBIAN_PACKAGE_SECTION libs) + set(CPACK_DEBIAN_PACKAGE_PRIORITY optional) + # set(CPACK_DEBIAN_PACKAGE_DEPENDS zlib1g) + set(CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS pkg-config git devscripts dh-make + openjdk-8-jdk) + if(NOT PPA_DEBIAN_VERSION) + set(PPA_DEBIAN_VERSION ppa1) + endif() + if(PROJECT_PPA) + set(DPUT_HOST ${PROJECT_PPA}) + endif() + # set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "any") + set(CPACK_DEBIAN_RESOURCE_FILE_CHANGELOG ${CMAKE_SOURCE_DIR}/Changelog) + set(CPACK_DEBIAN_UPDATE_CHANGELOG ON) +endif(PACKAGE_DEB) + +if(PACKAGE_TGZ) + set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF) +endif(PACKAGE_TGZ) + +include(CPack) + +if(ENABLE_PPA) + set(CPACK_SOURCE_INSTALL_SCRIPT + "${CMAKE_CURRENT_SOURCE_DIR}/../fetch_libs.cmake") + if(NOT PROJECT_PPA_DISTRIB_TARGET) + set(PROJECT_PPA_DISTRIB_TARGET focal) + endif() + set(DPUT_CONFIG_IN ${CMAKE_CURRENT_SOURCE_DIR}/../debian/dput.cf.in) + include(UploadPPA) +endif() diff --git a/ios-tc.cmake b/ios-tc.cmake new file mode 100644 index 0000000..f266dd7 --- /dev/null +++ b/ios-tc.cmake @@ -0,0 +1,704 @@ +# This file is part of the ios-cmake project. It was retrieved from +# https://github.com/cristeab/ios-cmake.git, which is a fork of +# https://code.google.com/p/ios-cmake/. Which in turn is based off of +# the Platform/Darwin.cmake and Platform/UnixPaths.cmake files which +# are included with CMake 2.8.4 +# +# The ios-cmake project is licensed under the new BSD license. +# +# Copyright (c) 2014, Bogdan Cristea and LTE Engineering Software, +# Kitware, Inc., Insight Software Consortium. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# This file is based off of the Platform/Darwin.cmake and +# Platform/UnixPaths.cmake files which are included with CMake 2.8.4 +# It has been altered for iOS development. +# +# Updated by Alex Stewart (alexs.mac@gmail.com) +# +# ***************************************************************************** +# Now maintained by Alexander Widerberg (widerbergaren [at] gmail.com) +# under the BSD-3-Clause license +# https://github.com/leetal/ios-cmake +# ***************************************************************************** +# +# INFORMATION / HELP +# +# The following arguments control the behaviour of this toolchain: +# +# PLATFORM: (default "OS") +# OS = Build for iPhoneOS. +# OS64 = Build for arm64 iphoneOS. +# OS64COMBINED = Build for arm64 x86_64 iphoneOS. Combined into FAT STATIC lib (supported on 3.14+ of CMakewith "-G Xcode" argument ONLY) +# SIMULATOR = Build for x86 i386 iphoneOS Simulator. +# SIMULATOR64 = Build for x86_64 iphoneOS Simulator. +# TVOS = Build for arm64 tvOS. +# TVOSCOMBINED = Build for arm64 x86_64 tvOS. Combined into FAT STATIC lib (supported on 3.14+ of CMake with "-G Xcode" argument ONLY) +# SIMULATOR_TVOS = Build for x86_64 tvOS Simulator. +# WATCHOS = Build for armv7k arm64_32 for watchOS. +# WATCHOSCOMBINED = Build for armv7k arm64_32 x86_64 watchOS. Combined into FAT STATIC lib (supported on 3.14+ of CMake with "-G Xcode" argument ONLY) +# SIMULATOR_WATCHOS = Build for x86_64 for watchOS Simulator. +# +# CMAKE_OSX_SYSROOT: Path to the SDK to use. By default this is +# automatically determined from PLATFORM and xcodebuild, but +# can also be manually specified (although this should not be required). +# +# CMAKE_DEVELOPER_ROOT: Path to the Developer directory for the platform +# being compiled for. By default this is automatically determined from +# CMAKE_OSX_SYSROOT, but can also be manually specified (although this should +# not be required). +# +# DEPLOYMENT_TARGET: Minimum SDK version to target. Default 2.0 on watchOS and 9.0 on tvOS+iOS +# +# ENABLE_BITCODE: (1|0) Enables or disables bitcode support. Default 1 (true) +# +# ENABLE_ARC: (1|0) Enables or disables ARC support. Default 1 (true, ARC enabled by default) +# +# ENABLE_VISIBILITY: (1|0) Enables or disables symbol visibility support. Default 0 (false, visibility hidden by default) +# +# ENABLE_STRICT_TRY_COMPILE: (1|0) Enables or disables strict try_compile() on all Check* directives (will run linker +# to actually check if linking is possible). Default 0 (false, will set CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY) +# +# ARCHS: (armv7 armv7s armv7k arm64 arm64_32 i386 x86_64) If specified, will override the default architectures for the given PLATFORM +# OS = armv7 armv7s arm64 (if applicable) +# OS64 = arm64 (if applicable) +# SIMULATOR = i386 +# SIMULATOR64 = x86_64 +# TVOS = arm64 +# SIMULATOR_TVOS = x86_64 (i386 has since long been deprecated) +# WATCHOS = armv7k arm64_32 (if applicable) +# SIMULATOR_WATCHOS = x86_64 (i386 has since long been deprecated) +# +# This toolchain defines the following variables for use externally: +# +# XCODE_VERSION: Version number (not including Build version) of Xcode detected. +# SDK_VERSION: Version of SDK being used. +# CMAKE_OSX_ARCHITECTURES: Architectures being compiled for (generated from PLATFORM). +# +# This toolchain defines the following macros for use externally: +# +# set_xcode_property (TARGET XCODE_PROPERTY XCODE_VALUE XCODE_VARIANT) +# A convenience macro for setting xcode specific properties on targets. +# Available variants are: All, Release, RelWithDebInfo, Debug, MinSizeRel +# example: set_xcode_property (myioslib IPHONEOS_DEPLOYMENT_TARGET "3.1" "all"). +# +# find_host_package (PROGRAM ARGS) +# A macro used to find executable programs on the host system, not within the +# environment. Thanks to the android-cmake project for providing the +# command. +# +# ******************************** DEPRECATIONS ******************************* +# +# IOS_DEPLOYMENT_TARGET: (Deprecated) Alias to DEPLOYMENT_TARGET +# CMAKE_IOS_DEVELOPER_ROOT: (Deprecated) Alias to CMAKE_DEVELOPER_ROOT +# IOS_PLATFORM: (Deprecated) Alias to PLATFORM +# IOS_ARCH: (Deprecated) Alias to ARCHS +# +# ***************************************************************************** +# + +# Fix for PThread library not in path +set(CMAKE_THREAD_LIBS_INIT "-lpthread") +set(CMAKE_HAVE_THREADS_LIBRARY 1) +set(CMAKE_USE_WIN32_THREADS_INIT 0) +set(CMAKE_USE_PTHREADS_INIT 1) + +# Cache what generator is used +set(USED_CMAKE_GENERATOR "${CMAKE_GENERATOR}" CACHE STRING "Expose CMAKE_GENERATOR" FORCE) + +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14") + set(MODERN_CMAKE YES) +endif() + +# Get the Xcode version being used. +execute_process(COMMAND xcodebuild -version + OUTPUT_VARIABLE XCODE_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +string(REGEX MATCH "Xcode [0-9\\.]+" XCODE_VERSION "${XCODE_VERSION}") +string(REGEX REPLACE "Xcode ([0-9\\.]+)" "\\1" XCODE_VERSION "${XCODE_VERSION}") + +######## ALIASES (DEPRECATION WARNINGS) + +if(DEFINED IOS_PLATFORM) + set(PLATFORM ${IOS_PLATFORM}) + message(DEPRECATION "IOS_PLATFORM argument is DEPRECATED. Consider using the new PLATFORM argument instead.") +endif() + +if(DEFINED IOS_DEPLOYMENT_TARGET) + set(DEPLOYMENT_TARGET ${IOS_DEPLOYMENT_TARGET}) + message(DEPRECATION "IOS_DEPLOYMENT_TARGET argument is DEPRECATED. Consider using the new DEPLOYMENT_TARGET argument instead.") +endif() + +if(DEFINED CMAKE_IOS_DEVELOPER_ROOT) + set(CMAKE_DEVELOPER_ROOT ${CMAKE_IOS_DEVELOPER_ROOT}) + message(DEPRECATION "CMAKE_IOS_DEVELOPER_ROOT argument is DEPRECATED. Consider using the new CMAKE_DEVELOPER_ROOT argument instead.") +endif() + +if(DEFINED IOS_ARCH) + set(ARCHS ${IOS_ARCH}) + message(DEPRECATION "IOS_ARCH argument is DEPRECATED. Consider using the new ARCHS argument instead.") +endif() + +######## END ALIASES + +# Unset the FORCE on cache variables if in try_compile() +set(FORCE_CACHE FORCE) +get_property(_CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE) +if(_CMAKE_IN_TRY_COMPILE) + unset(FORCE_CACHE) +endif() + +# Default to building for iPhoneOS if not specified otherwise, and we cannot +# determine the platform from the CMAKE_OSX_ARCHITECTURES variable. The use +# of CMAKE_OSX_ARCHITECTURES is such that try_compile() projects can correctly +# determine the value of PLATFORM from the root project, as +# CMAKE_OSX_ARCHITECTURES is propagated to them by CMake. +if(NOT DEFINED PLATFORM) + if (CMAKE_OSX_ARCHITECTURES) + if(CMAKE_OSX_ARCHITECTURES MATCHES ".*arm.*" AND CMAKE_OSX_SYSROOT MATCHES ".*iphoneos.*") + set(PLATFORM "OS") + elseif(CMAKE_OSX_ARCHITECTURES MATCHES "i386" AND CMAKE_OSX_SYSROOT MATCHES ".*iphonesimulator.*") + set(PLATFORM "SIMULATOR") + elseif(CMAKE_OSX_ARCHITECTURES MATCHES "x86_64" AND CMAKE_OSX_SYSROOT MATCHES ".*iphonesimulator.*") + set(PLATFORM "SIMULATOR64") + elseif(CMAKE_OSX_ARCHITECTURES MATCHES "arm64" AND CMAKE_OSX_SYSROOT MATCHES ".*appletvos.*") + set(PLATFORM "TVOS") + elseif(CMAKE_OSX_ARCHITECTURES MATCHES "x86_64" AND CMAKE_OSX_SYSROOT MATCHES ".*appletvsimulator.*") + set(PLATFORM "SIMULATOR_TVOS") + elseif(CMAKE_OSX_ARCHITECTURES MATCHES ".*armv7k.*" AND CMAKE_OSX_SYSROOT MATCHES ".*watchos.*") + set(PLATFORM "WATCHOS") + elseif(CMAKE_OSX_ARCHITECTURES MATCHES "i386" AND CMAKE_OSX_SYSROOT MATCHES ".*watchsimulator.*") + set(PLATFORM "SIMULATOR_WATCHOS") + endif() + endif() + if (NOT PLATFORM) + set(PLATFORM "OS") + endif() +endif() + +set(PLATFORM_INT "${PLATFORM}" CACHE STRING "Type of platform for which the build targets.") + +# Handle the case where we are targeting iOS and a version above 10.3.4 (32-bit support dropped officially) +if(PLATFORM_INT STREQUAL "OS" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4) + set(PLATFORM_INT "OS64") + message(STATUS "Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.") +elseif(PLATFORM_INT STREQUAL "SIMULATOR" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4) + set(PLATFORM_INT "SIMULATOR64") + message(STATUS "Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.") +endif() + +# Determine the platform name and architectures for use in xcodebuild commands +# from the specified PLATFORM name. +if(PLATFORM_INT STREQUAL "OS") + set(SDK_NAME iphoneos) + if(NOT ARCHS) + set(ARCHS armv7 armv7s arm64) + endif() +elseif(PLATFORM_INT STREQUAL "OS64") + set(SDK_NAME iphoneos) + if(NOT ARCHS) + if (XCODE_VERSION VERSION_GREATER 10.0) + set(ARCHS arm64) # Add arm64e when Apple have fixed the integration issues with it, libarclite_iphoneos.a is currently missung bitcode markers for example + else() + set(ARCHS arm64) + endif() + endif() +elseif(PLATFORM_INT STREQUAL "OS64COMBINED") + set(SDK_NAME iphoneos) + if(MODERN_CMAKE) + if(NOT ARCHS) + if (XCODE_VERSION VERSION_GREATER 10.0) + set(ARCHS arm64 x86_64) # Add arm64e when Apple have fixed the integration issues with it, libarclite_iphoneos.a is currently missung bitcode markers for example + else() + set(ARCHS arm64 x86_64) + endif() + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the OS64COMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR") + set(SDK_NAME iphonesimulator) + if(NOT ARCHS) + set(ARCHS i386) + endif() + message(DEPRECATION "SIMULATOR IS DEPRECATED. Consider using SIMULATOR64 instead.") +elseif(PLATFORM_INT STREQUAL "SIMULATOR64") + set(SDK_NAME iphonesimulator) + if(NOT ARCHS) + set(ARCHS x86_64) + endif() +elseif(PLATFORM_INT STREQUAL "TVOS") + set(SDK_NAME appletvos) + if(NOT ARCHS) + set(ARCHS arm64) + endif() +elseif (PLATFORM_INT STREQUAL "TVOSCOMBINED") + set(SDK_NAME appletvos) + if(MODERN_CMAKE) + if(NOT ARCHS) + set(ARCHS arm64 x86_64) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the TVOSCOMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR_TVOS") + set(SDK_NAME appletvsimulator) + if(NOT ARCHS) + set(ARCHS x86_64) + endif() +elseif(PLATFORM_INT STREQUAL "WATCHOS") + set(SDK_NAME watchos) + if(NOT ARCHS) + if (XCODE_VERSION VERSION_GREATER 10.0) + set(ARCHS armv7k arm64_32) + else() + set(ARCHS armv7k) + endif() + endif() +elseif(PLATFORM_INT STREQUAL "WATCHOSCOMBINED") + set(SDK_NAME watchos) + if(MODERN_CMAKE) + if(NOT ARCHS) + if (XCODE_VERSION VERSION_GREATER 10.0) + set(ARCHS armv7k arm64_32 i386) + else() + set(ARCHS armv7k i386) + endif() + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the WATCHOSCOMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS") + set(SDK_NAME watchsimulator) + if(NOT ARCHS) + set(ARCHS i386) + endif() +else() + message(FATAL_ERROR "Invalid PLATFORM: ${PLATFORM_INT}") +endif() + +if(MODERN_CMAKE AND PLATFORM_INT MATCHES ".*COMBINED" AND NOT USED_CMAKE_GENERATOR MATCHES "Xcode") + message(FATAL_ERROR "The COMBINED options only work with Xcode generator, -G Xcode") +endif() + +# If user did not specify the SDK root to use, then query xcodebuild for it. +execute_process(COMMAND xcodebuild -version -sdk ${SDK_NAME} Path + OUTPUT_VARIABLE CMAKE_OSX_SYSROOT_INT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +if (NOT DEFINED CMAKE_OSX_SYSROOT_INT AND NOT DEFINED CMAKE_OSX_SYSROOT) + message(SEND_ERROR "Please make sure that Xcode is installed and that the toolchain" + "is pointing to the correct path. Please run:" + "sudo xcode-select -s /Applications/Xcode.app/Contents/Developer" + "and see if that fixes the problem for you.") + message(FATAL_ERROR "Invalid CMAKE_OSX_SYSROOT: ${CMAKE_OSX_SYSROOT} " + "does not exist.") +elseif(DEFINED CMAKE_OSX_SYSROOT_INT) + set(CMAKE_OSX_SYSROOT "${CMAKE_OSX_SYSROOT_INT}" CACHE INTERNAL "") +endif() + +# Set Xcode property for SDKROOT as well if Xcode generator is used +if(USED_CMAKE_GENERATOR MATCHES "Xcode") + set(CMAKE_OSX_SYSROOT "${SDK_NAME}" CACHE INTERNAL "") + if(NOT DEFINED CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM) + set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "123456789A" CACHE INTERNAL "") + endif() +endif() + +# Specify minimum version of deployment target. +if(NOT DEFINED DEPLOYMENT_TARGET) + if (PLATFORM_INT STREQUAL "WATCHOS" OR PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS") + # Unless specified, SDK version 2.0 is used by default as minimum target version (watchOS). + set(DEPLOYMENT_TARGET "2.0" + CACHE STRING "Minimum SDK version to build for." ) + else() + # Unless specified, SDK version 9.0 is used by default as minimum target version (iOS, tvOS). + set(DEPLOYMENT_TARGET "9.0" + CACHE STRING "Minimum SDK version to build for." ) + endif() + message(STATUS "Using the default min-version since DEPLOYMENT_TARGET not provided!") +endif() + +# Use bitcode or not +if(NOT DEFINED ENABLE_BITCODE AND NOT ARCHS MATCHES "((^|;|, )(i386|x86_64))+") + # Unless specified, enable bitcode support by default + message(STATUS "Enabling bitcode support by default. ENABLE_BITCODE not provided!") + set(ENABLE_BITCODE TRUE) +elseif(NOT DEFINED ENABLE_BITCODE) + message(STATUS "Disabling bitcode support by default on simulators. ENABLE_BITCODE not provided for override!") + set(ENABLE_BITCODE FALSE) +endif() +set(ENABLE_BITCODE_INT ${ENABLE_BITCODE} CACHE BOOL "Whether or not to enable bitcode" ${FORCE_CACHE}) +# Use ARC or not +if(NOT DEFINED ENABLE_ARC) + # Unless specified, enable ARC support by default + set(ENABLE_ARC TRUE) + message(STATUS "Enabling ARC support by default. ENABLE_ARC not provided!") +endif() +set(ENABLE_ARC_INT ${ENABLE_ARC} CACHE BOOL "Whether or not to enable ARC" ${FORCE_CACHE}) +# Use hidden visibility or not +if(NOT DEFINED ENABLE_VISIBILITY) + # Unless specified, disable symbols visibility by default + set(ENABLE_VISIBILITY FALSE) + message(STATUS "Hiding symbols visibility by default. ENABLE_VISIBILITY not provided!") +endif() +set(ENABLE_VISIBILITY_INT ${ENABLE_VISIBILITY} CACHE BOOL "Whether or not to hide symbols (-fvisibility=hidden)" ${FORCE_CACHE}) +# Set strict compiler checks or not +if(NOT DEFINED ENABLE_STRICT_TRY_COMPILE) + # Unless specified, disable strict try_compile() + set(ENABLE_STRICT_TRY_COMPILE FALSE) + message(STATUS "Using NON-strict compiler checks by default. ENABLE_STRICT_TRY_COMPILE not provided!") +endif() +set(ENABLE_STRICT_TRY_COMPILE_INT ${ENABLE_STRICT_TRY_COMPILE} CACHE BOOL "Whether or not to use strict compiler checks" ${FORCE_CACHE}) +# Get the SDK version information. +execute_process(COMMAND xcodebuild -sdk ${CMAKE_OSX_SYSROOT} -version SDKVersion + OUTPUT_VARIABLE SDK_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + +# Find the Developer root for the specific iOS platform being compiled for +# from CMAKE_OSX_SYSROOT. Should be ../../ from SDK specified in +# CMAKE_OSX_SYSROOT. There does not appear to be a direct way to obtain +# this information from xcrun or xcodebuild. +if (NOT DEFINED CMAKE_DEVELOPER_ROOT AND NOT USED_CMAKE_GENERATOR MATCHES "Xcode") + get_filename_component(PLATFORM_SDK_DIR ${CMAKE_OSX_SYSROOT} PATH) + get_filename_component(CMAKE_DEVELOPER_ROOT ${PLATFORM_SDK_DIR} PATH) + + if (NOT DEFINED CMAKE_DEVELOPER_ROOT) + message(FATAL_ERROR "Invalid CMAKE_DEVELOPER_ROOT: " + "${CMAKE_DEVELOPER_ROOT} does not exist.") + endif() +endif() +# Find the C & C++ compilers for the specified SDK. +if(NOT CMAKE_C_COMPILER) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find clang + OUTPUT_VARIABLE CMAKE_C_COMPILER + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + message(STATUS "Using C compiler: ${CMAKE_C_COMPILER}") +endif() +if(NOT CMAKE_CXX_COMPILER) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find clang++ + OUTPUT_VARIABLE CMAKE_CXX_COMPILER + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + message(STATUS "Using CXX compiler: ${CMAKE_CXX_COMPILER}") +endif() +# Find (Apple's) libtool. +execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find libtool + OUTPUT_VARIABLE BUILD_LIBTOOL + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +message(STATUS "Using libtool: ${BUILD_LIBTOOL}") +# Configure libtool to be used instead of ar + ranlib to build static libraries. +# This is required on Xcode 7+, but should also work on previous versions of +# Xcode. +set(CMAKE_C_CREATE_STATIC_LIBRARY + "${BUILD_LIBTOOL} -static -o ") +set(CMAKE_CXX_CREATE_STATIC_LIBRARY + "${BUILD_LIBTOOL} -static -o ") +# Find the toolchain's provided install_name_tool if none is found on the host +if(NOT CMAKE_INSTALL_NAME_TOOL) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find install_name_tool + OUTPUT_VARIABLE CMAKE_INSTALL_NAME_TOOL_INT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + set(CMAKE_INSTALL_NAME_TOOL ${CMAKE_INSTALL_NAME_TOOL_INT} CACHE STRING "" ${FORCE_CACHE}) +endif() +# Get the version of Darwin (OS X) of the host. +execute_process(COMMAND uname -r + OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +if(SDK_NAME MATCHES "iphone") + set(CMAKE_SYSTEM_NAME iOS CACHE INTERNAL "" ${FORCE_CACHE}) +endif() +# CMake 3.14+ support building for iOS, watchOS and tvOS out of the box. +if(MODERN_CMAKE) + if(SDK_NAME MATCHES "appletv") + set(CMAKE_SYSTEM_NAME tvOS CACHE INTERNAL "" ${FORCE_CACHE}) + elseif(SDK_NAME MATCHES "watch") + set(CMAKE_SYSTEM_NAME watchOS CACHE INTERNAL "" ${FORCE_CACHE}) + endif() + # Provide flags for a combined FAT library build on newer CMake versions + if(PLATFORM_INT MATCHES ".*COMBINED") + set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH "NO" CACHE INTERNAL "" ${FORCE_CACHE}) + set(CMAKE_IOS_INSTALL_COMBINED YES CACHE INTERNAL "" ${FORCE_CACHE}) + message(STATUS "Will combine built (static) artifacts into FAT lib...") + endif() +elseif(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.10") + # Legacy code path prior to CMake 3.14 or fallback if no SDK_NAME specified + set(CMAKE_SYSTEM_NAME iOS CACHE INTERNAL "" ${FORCE_CACHE}) +else() + # Legacy code path prior to CMake 3.14 or fallback if no SDK_NAME specified + set(CMAKE_SYSTEM_NAME Darwin CACHE INTERNAL "" ${FORCE_CACHE}) +endif() +# Standard settings. +set(CMAKE_SYSTEM_VERSION ${SDK_VERSION} CACHE INTERNAL "") +set(UNIX TRUE CACHE BOOL "") +set(APPLE TRUE CACHE BOOL "") +set(IOS TRUE CACHE BOOL "") +set(CMAKE_AR ar CACHE FILEPATH "" FORCE) +set(CMAKE_RANLIB ranlib CACHE FILEPATH "" FORCE) +set(CMAKE_STRIP strip CACHE FILEPATH "" FORCE) +# Set the architectures for which to build. +set(CMAKE_OSX_ARCHITECTURES ${ARCHS} CACHE STRING "Build architecture for iOS") +# Change the type of target generated for try_compile() so it'll work when cross-compiling, weak compiler checks +if(ENABLE_STRICT_TRY_COMPILE_INT) + message(STATUS "Using strict compiler checks (default in CMake).") +else() + set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +endif() +# All iOS/Darwin specific settings - some may be redundant. +set(CMAKE_MACOSX_BUNDLE YES) +set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO") +set(CMAKE_SHARED_LIBRARY_PREFIX "lib") +set(CMAKE_SHARED_LIBRARY_SUFFIX ".dylib") +set(CMAKE_SHARED_MODULE_PREFIX "lib") +set(CMAKE_SHARED_MODULE_SUFFIX ".so") +set(CMAKE_C_COMPILER_ABI ELF) +set(CMAKE_CXX_COMPILER_ABI ELF) +set(CMAKE_C_HAS_ISYSROOT 1) +set(CMAKE_CXX_HAS_ISYSROOT 1) +set(CMAKE_MODULE_EXISTS 1) +set(CMAKE_DL_LIBS "") +set(CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ") +set(CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ") +set(CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}") +set(CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}") + +if(ARCHS MATCHES "((^|;|, )(arm64|arm64e|x86_64))+") + set(CMAKE_C_SIZEOF_DATA_PTR 8) + set(CMAKE_CXX_SIZEOF_DATA_PTR 8) + if(ARCHS MATCHES "((^|;|, )(arm64|arm64e))+") + set(CMAKE_SYSTEM_PROCESSOR "aarch64") + else() + set(CMAKE_SYSTEM_PROCESSOR "x86_64") + endif() +else() + set(CMAKE_C_SIZEOF_DATA_PTR 4) + set(CMAKE_CXX_SIZEOF_DATA_PTR 4) + set(CMAKE_SYSTEM_PROCESSOR "arm") +endif() + +# Note that only Xcode 7+ supports the newer more specific: +# -m${SDK_NAME}-version-min flags, older versions of Xcode use: +# -m(ios/ios-simulator)-version-min instead. +if(${CMAKE_VERSION} VERSION_LESS "3.11") + if(PLATFORM_INT STREQUAL "OS" OR PLATFORM_INT STREQUAL "OS64") + if(XCODE_VERSION VERSION_LESS 7.0) + set(SDK_NAME_VERSION_FLAGS + "-mios-version-min=${DEPLOYMENT_TARGET}") + else() + # Xcode 7.0+ uses flags we can build directly from SDK_NAME. + set(SDK_NAME_VERSION_FLAGS + "-m${SDK_NAME}-version-min=${DEPLOYMENT_TARGET}") + endif() + elseif(PLATFORM_INT STREQUAL "TVOS") + set(SDK_NAME_VERSION_FLAGS + "-mtvos-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "SIMULATOR_TVOS") + set(SDK_NAME_VERSION_FLAGS + "-mtvos-simulator-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "WATCHOS") + set(SDK_NAME_VERSION_FLAGS + "-mwatchos-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS") + set(SDK_NAME_VERSION_FLAGS + "-mwatchos-simulator-version-min=${DEPLOYMENT_TARGET}") + else() + # SIMULATOR or SIMULATOR64 both use -mios-simulator-version-min. + set(SDK_NAME_VERSION_FLAGS + "-mios-simulator-version-min=${DEPLOYMENT_TARGET}") + endif() +else() + # Newer versions of CMake sets the version min flags correctly + set(CMAKE_OSX_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET} CACHE STRING + "Set CMake deployment target" ${FORCE_CACHE}) +endif() + + +if(ENABLE_BITCODE_INT) + set(BITCODE "-fembed-bitcode") + set(CMAKE_XCODE_ATTRIBUTE_BITCODE_GENERATION_MODE "bitcode" CACHE INTERNAL "") + set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "YES" CACHE INTERNAL "") +else() + set(BITCODE "") + set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO" CACHE INTERNAL "") +endif() + +if(ENABLE_ARC_INT) + set(FOBJC_ARC "-fobjc-arc") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC "YES" CACHE INTERNAL "") +else() + set(FOBJC_ARC "-fno-objc-arc") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC "NO" CACHE INTERNAL "") +endif() + +if(NOT ENABLE_VISIBILITY_INT) + set(VISIBILITY "-fvisibility=hidden") + set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN "YES" CACHE INTERNAL "") +else() + set(VISIBILITY "") + set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN "NO" CACHE INTERNAL "") +endif() + +if(NOT IOS_TOOLCHAIN_HAS_RUN) + #Check if Xcode generator is used, since that will handle these flags automagically + if(USED_CMAKE_GENERATOR MATCHES "Xcode") + message(STATUS "Not setting any manual command-line buildflags, since Xcode is selected as generator.") + else() + set(CMAKE_C_FLAGS + "${SDK_NAME_VERSION_FLAGS} ${BITCODE} -fobjc-abi-version=2 ${FOBJC_ARC} ${CMAKE_C_FLAGS}") + # Hidden visibilty is required for C++ on iOS. + set(CMAKE_CXX_FLAGS + "${SDK_NAME_VERSION_FLAGS} ${BITCODE} ${VISIBILITY} -fvisibility-inlines-hidden -fobjc-abi-version=2 ${FOBJC_ARC} ${CMAKE_CXX_FLAGS}") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -O0 -g ${CMAKE_CXX_FLAGS_DEBUG}") + set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS} -DNDEBUG -Os -ffast-math ${CMAKE_CXX_FLAGS_MINSIZEREL}") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS} -DNDEBUG -O2 -g -ffast-math ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -DNDEBUG -O3 -ffast-math ${CMAKE_CXX_FLAGS_RELEASE}") + set(CMAKE_C_LINK_FLAGS "${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}") + set(CMAKE_CXX_LINK_FLAGS "${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}") + set(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp") + + # In order to ensure that the updated compiler flags are used in try_compile() + # tests, we have to forcibly set them in the CMake cache, not merely set them + # in the local scope. + set(VARS_TO_FORCE_IN_CACHE + CMAKE_C_FLAGS + CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS_MINSIZEREL + CMAKE_CXX_FLAGS_RELEASE + CMAKE_C_LINK_FLAGS + CMAKE_CXX_LINK_FLAGS) + foreach(VAR_TO_FORCE ${VARS_TO_FORCE_IN_CACHE}) + set(${VAR_TO_FORCE} "${${VAR_TO_FORCE}}" CACHE STRING "" ${FORCE_CACHE}) + endforeach() + endif() + + ## Print status messages to inform of the current state + message(STATUS "Configuring ${SDK_NAME} build for platform: ${PLATFORM_INT}, architecture(s): ${ARCHS}") + message(STATUS "Using SDK: ${CMAKE_OSX_SYSROOT_INT}") + message(STATUS "Using minimum deployment version: ${DEPLOYMENT_TARGET}" + " (SDK version: ${SDK_VERSION})") + if(MODERN_CMAKE) + message(STATUS "Merging integrated CMake 3.14+ iOS,tvOS,watchOS,macOS toolchain(s) with this toolchain!") + endif() + if(USED_CMAKE_GENERATOR MATCHES "Xcode") + message(STATUS "Using Xcode version: ${XCODE_VERSION}") + endif() + if(DEFINED SDK_NAME_VERSION_FLAGS) + message(STATUS "Using version flags: ${SDK_NAME_VERSION_FLAGS}") + endif() + message(STATUS "Using a data_ptr size of: ${CMAKE_CXX_SIZEOF_DATA_PTR}") + message(STATUS "Using install_name_tool: ${CMAKE_INSTALL_NAME_TOOL}") + if(ENABLE_BITCODE_INT) + message(STATUS "Enabling bitcode support.") + else() + message(STATUS "Disabling bitcode support.") + endif() + + if(ENABLE_ARC_INT) + message(STATUS "Enabling ARC support.") + else() + message(STATUS "Disabling ARC support.") + endif() + + if(NOT ENABLE_VISIBILITY_INT) + message(STATUS "Hiding symbols (-fvisibility=hidden).") + endif() +endif() + +set(CMAKE_PLATFORM_HAS_INSTALLNAME 1) +set(CMAKE_SHARED_LINKER_FLAGS "-rpath @executable_path/Frameworks -rpath @loader_path/Frameworks") +set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib -Wl,-headerpad_max_install_names") +set(CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle -Wl,-headerpad_max_install_names") +set(CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,") +set(CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,") +set(CMAKE_FIND_LIBRARY_SUFFIXES ".tbd" ".dylib" ".so" ".a") +set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG "-install_name") + +# Set the find root to the iOS developer roots and to user defined paths. +set(CMAKE_FIND_ROOT_PATH ${CMAKE_OSX_SYSROOT_INT} ${CMAKE_PREFIX_PATH} CACHE STRING "Root path that will be prepended + to all search paths") +# Default to searching for frameworks first. +set(CMAKE_FIND_FRAMEWORK FIRST) +# Set up the default search directories for frameworks. +set(CMAKE_FRAMEWORK_PATH + ${CMAKE_DEVELOPER_ROOT}/Library/PrivateFrameworks + ${CMAKE_OSX_SYSROOT_INT}/System/Library/Frameworks + ${CMAKE_FRAMEWORK_PATH} CACHE STRING "Frameworks search paths" ${FORCE_CACHE}) + +set(IOS_TOOLCHAIN_HAS_RUN TRUE CACHE BOOL "Has the CMake toolchain run already?" ${FORCE_CACHE}) + +# By default, search both the specified iOS SDK and the remainder of the host filesystem. +if(NOT CMAKE_FIND_ROOT_PATH_MODE_PROGRAM) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH CACHE STRING "" ${FORCE_CACHE}) +endif() +if(NOT CMAKE_FIND_ROOT_PATH_MODE_LIBRARY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY CACHE STRING "" ${FORCE_CACHE}) +endif() +if(NOT CMAKE_FIND_ROOT_PATH_MODE_INCLUDE) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY CACHE STRING "" ${FORCE_CACHE}) +endif() +if(NOT CMAKE_FIND_ROOT_PATH_MODE_PACKAGE) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY CACHE STRING "" ${FORCE_CACHE}) +endif() + +# +# Some helper-macros below to simplify and beautify the CMakeFile +# + +# This little macro lets you set any Xcode specific property. +macro(set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE XCODE_RELVERSION) + set(XCODE_RELVERSION_I "${XCODE_RELVERSION}") + if(XCODE_RELVERSION_I STREQUAL "All") + set_property(TARGET ${TARGET} PROPERTY + XCODE_ATTRIBUTE_${XCODE_PROPERTY} "${XCODE_VALUE}") + else() + set_property(TARGET ${TARGET} PROPERTY + XCODE_ATTRIBUTE_${XCODE_PROPERTY}[variant=${XCODE_RELVERSION_I}] "${XCODE_VALUE}") + endif() +endmacro(set_xcode_property) +# This macro lets you find executable programs on the host system. +macro(find_host_package) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER) + set(IOS FALSE) + find_package(${ARGN}) + set(IOS TRUE) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) +endmacro(find_host_package) diff --git a/man/CMakeLists.txt b/man/CMakeLists.txt new file mode 100644 index 0000000..34b65bc --- /dev/null +++ b/man/CMakeLists.txt @@ -0,0 +1,10 @@ + +if (DO_INSTALL_CORE) + + install(FILES ejdb2.3 DESTINATION ${CMAKE_INSTALL_MANDIR}/man3 + COMPONENT doc) + + install(FILES jbs.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 + COMPONENT doc) + + endif() \ No newline at end of file diff --git a/man/ejdb2.3 b/man/ejdb2.3 new file mode 100644 index 0000000..5650a62 --- /dev/null +++ b/man/ejdb2.3 @@ -0,0 +1,12 @@ +.TH "EJDB2" 3 "2020-04-01" "Man Page" "EJDB2" + +.SH NAME +EJDB2 \- Embedded JSON Database engine + +.SH DESCRIPTION +.PP +EJDB2 is an embeddable JSON database engine published under MIT license. + +.SH "SEE ALSO" +.I http://ejdb.org +EJDB project offcial web site. diff --git a/man/jbs.1 b/man/jbs.1 new file mode 100644 index 0000000..5097b57 --- /dev/null +++ b/man/jbs.1 @@ -0,0 +1,12 @@ +.TH "JBS" 1 "2019-04-01" "Man Page" "JBS" + +.SH NAME +EJDB2 standalone HTTP REST/Websocket server. + +.SH DESCRIPTION +.PP +EJDB2 is an embeddable JSON database engine published under MIT license. + +.SH "SEE ALSO" +.I http://ejdb.org +EJDB2 project offcial web site. diff --git a/pvs_studio_analyze.sh b/pvs_studio_analyze.sh new file mode 100755 index 0000000..6be8850 --- /dev/null +++ b/pvs_studio_analyze.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +SCRIPTPATH="$( + cd "$(dirname "$0")" + pwd -P +)" +cd $SCRIPTPATH + +if [ ! -d ./build ]; then + mkdir ./build +fi + +cd ./build +cmake .. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_JNI_BINDING=ON \ + -DBUILD_DART_BINDING=ON \ + -DBUILD_NODEJS_BINDING=ON \ + && make +pvs-studio-analyzer analyze -a 45 -l ${HOME}/.config/PVS-Studio/PVS-Studio.lic -j4 -o ./pvs.log +rm -rf ./pvs_report +plog-converter -a 'GA:1,2,3;64:1;OP:1,2,3;MISRA:1' -t fullhtml -o ./pvs_report ./pvs.log diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..1124c68 --- /dev/null +++ b/release.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +set -e +# set -x + +SCRIPTPATH="$( + cd "$(dirname "$0")" + pwd -P +)" +cd $SCRIPTPATH + + +readme() { + echo "Generating README.md"; + cat "./BASE.md" > "./README.md" + echo -e "\n\n" >> "./README.md" + cat "./src/bindings/ejdb2_android/README.md" >> "./README.md" + echo -e "\n\n" >> "./README.md" + # cat "./src/bindings/ejdb2_swift/EJDB2Swift/README.md" >> "./README.md" + # echo -e "\n\n" >> "./README.md" + cat "./src/jql/README.md" >> "./README.md" + echo -e "\n\n" >> "./README.md" + cat "./src/jbr/README.md" >> "./README.md" + echo -e "\n\n" >> "./README.md" + cat "./docker/README.md" >> "./README.md" + echo -e "\n\n" >> "./README.md" + cat "./CAPI.md" >> "./README.md" + echo -e '\n# License\n```\n' >> "./README.md" + cat "./LICENSE" >> "./README.md" + echo -e '\n```\n' >> "./README.md" +} + +release_tag() { + echo "Creating EJDB2 release" + readme + + git pull origin master + dch --distribution testing --no-force-save-on-release --release "" -c ./Changelog + VERSION=`dpkg-parsechangelog -l./Changelog -SVersion` + TAG="v${VERSION}" + CHANGESET=`dpkg-parsechangelog -l./Changelog -SChanges | sed '/^ejdb2.*/d' | sed '/^\s*$/d'` + git add ./Changelog + git add ./README.md + + if ! git diff-index --quiet HEAD --; then + git commit -a -m"${TAG} landed" + git push origin master + fi + + echo "${CHANGESET}" | git tag -f -a -F - "${TAG}" + git push origin -f --tags +} + +while [ "$1" != "" ]; do + case $1 in + "-d" ) readme + exit + ;; + "-r" ) release_tag + exit + ;; + esac + shift +done + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..af0bc62 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,383 @@ +if(NOT CMAKE_BUILD_TYPE) + message( + FATAL_ERROR + "Please specify the build type -DCMAKE_BUILD_TYPE=Debug|Release|RelWithDebInfo" + ) +endif() + +set(MODULES util jbl jql jbi) +set(PROJECT_LLIBRARIES) +set(PROJECT_INCLUDE_DIRS) +set(ALL_SRC) +set(ALL_HDRS) +set(PUB_HDRS) +set(PROJECT_GENERATED_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated) +list(APPEND PROJECT_INCLUDE_DIRS "${PROJECT_GENERATED_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}") + +if(APPLE) + option(BUILD_FRAMEWORK "Build an OS X framework" OFF) + set(FRAMEWORK_INSTALL_DIR + "/Library/Frameworks" + CACHE STRING "Directory to install frameworks to.") +endif() + +include(CheckIncludeFile) +include(CheckIncludeFiles) +include(CheckLibraryExists) +include(TestBigEndian) + +include(AddIOWOW) + +if(ENABLE_HTTP) + if(WIN32) + message(FATAL_ERROR "ENABLE_HTTP option cannot be used in Windows build") + endif() + include(AddFacil) + add_definitions(-DJB_HTTP) + list(APPEND MODULES jbr) +endif() + +if((CMAKE_BUILD_TYPE EQUAL Release) OR (CMAKE_BUILD_TYPE EQUAL RelWithDebInfo)) + add_definition(-DJB_RELEASE=1) +endif() + +include(TestQsortR) +if(HAVE_QSORT_R) + add_definitions(-DJB_HAVE_QSORT_R) +endif() + +test_big_endian(IS_BIG_ENDIAN) +if(IS_BIG_ENDIAN EQUAL 1) + add_definitions(-DIW_BIGENDIAN=1) +endif() + +if(CMAKE_SIZEOF_VOID_P MATCHES 8) + add_definitions(-DIW_64) +else() + add_definitions(-DIW_32) +endif() + +if(BUILD_TESTS) + add_definitions(-DIW_TESTS) +endif() + +find_package(Threads REQUIRED CMAKE_THREAD_PREFER_PTHREAD) +if(CMAKE_USE_WIN32_THREADS_INIT) + add_definitions(-DJB_WIN32_THREADS) +elseif(CMAKE_USE_PTHREADS_INIT) + add_definitions(-DJB_PTHREADS) +else() + mesage(FATAL_ERROR "Unable to find suitable threading library") +endif(CMAKE_USE_WIN32_THREADS_INIT) + +if(ANDROID) + find_library(LOG_LIB log) + if(NOT LOG_LIB) + message(FATAL_ERROR "Library 'log' not FOUND") + endif() + list(APPEND PROJECT_LLIBRARIES "${LOG_LIB}") +endif() + +if(NOT WIN32) + list(APPEND PROJECT_LLIBRARIES ${CMAKE_THREAD_LIBS_INIT}) +else() + include(Win32LIBTools) + check_include_file(windows.h HAVE_WINDOWS_H) + if(NOT HAVE_WINDOWS_H) + message(FATAL_ERROR "Unable to find windows.h include file") + endif() + + set(IBERTY_FIND_REQUIRED ON) + include(FindLibIberty) + list(APPEND PROJECT_LLIBRARIES ${IBERTY_LIBRARIES}) + + check_library_exists(winpthread pthread_exit "" HAVE_WINPTHREAD) + if(NOT HAVE_WINPTHREAD) + message(FATAL_ERROR "Unable to winpthread lib") + endif() + list(INSERT PROJECT_LLIBRARIES 0 -lwinpthread) + add_definitions(-D_POSIX_THREAD_SAFE_FUNCTIONS) +endif() + +foreach(HF IN ITEMS stdlib stddef stdint stdbool) + string(TOUPPER "${HF}" UHF) + check_include_file(${HF}.h "JB_HAVE_${UHF}") + if(NOT JB_HAVE_${UHF}) + message(FATAL_ERROR "Include file '${HF}.h' not FOUND") + endif() +endforeach() + +add_definitions(-D_XOPEN_SOURCE=600) +add_definitions(-D_DEFAULT_SOURCE) +add_definitions(-D_LARGEFILE_SOURCE) +add_definitions(-D_FILE_OFFSET_BITS=64) +if(APPLE) + add_definitions(-D_DARWIN_C_SOURCE) +endif(APPLE) + +file(GLOB ROOT_SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.c) +list(APPEND ALL_SRC ${ROOT_SRC}) + +foreach(MODULE IN LISTS MODULES) + file(GLOB MODULE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}/*.c) + file(GLOB MODULE_HDRS ${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}/*.h) + list(APPEND ALL_SRC ${MODULE_SRC}) + list(APPEND ALL_HDRS ${MODULE_HDRS}) + list(APPEND PROJECT_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}) +endforeach(MODULE) + +list( + APPEND + PUB_HDRS + ${CMAKE_CURRENT_SOURCE_DIR}/ejdb2.h + ${CMAKE_CURRENT_SOURCE_DIR}/jbl/jbl.h + ${CMAKE_CURRENT_SOURCE_DIR}/jbr/jbr.h + ${CMAKE_CURRENT_SOURCE_DIR}/jql/jql.h + ${CMAKE_CURRENT_SOURCE_DIR}/util/lwre.h) + +set(CMAKE_C_FLAGS + "${CMAKE_C_FLAGS} -std=gnu11 -fsigned-char -pedantic -Wfatal-errors -Wall -Wextra \ + -Wno-sign-compare -Wno-unused-parameter -Wno-unknown-pragmas -Wno-unused-function \ + -Wno-missing-field-initializers -Wno-missing-braces \ + -Wno-shorten-64-to-32") + +if(NOT WIN32) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-implicit-fallthrough -fPIC") +else() + add_definitions(-D__USE_MINGW_ANSI_STDIO) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-pedantic-ms-format") + set(CMAKE_EXE_LINKER_FLAGS + "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++") +endif() + +if(ASAN) + set(CMAKE_C_ASAN "-fsanitize=address -fno-omit-frame-pointer") +endif() + +set(CMAKE_C_FLAGS_DEBUG + "${CMAKE_C_ASAN} -O0 -g -ggdb -Werror -DDEBUG -D_DEBUG -UNDEBUG -Wno-unused-variable" +) +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_ASAN} -O3 -DNDEBUG") +# set(CMAKE_EXE_LINKER_FLAGS_RELEASE "-Wl,-s") +set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELEASE} -g") +set(CMAKE_C_FLAGS_RELEASEWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") + +if(CMAKE_COMPILER_IS_GNUCC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") +endif() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tmpl/ejdb2cfg.h + ${PROJECT_GENERATED_DIR}/ejdb2cfg.h) +file(GLOB PROJECT_GENERATED_HDRS ${PROJECT_GENERATED_DIR}/*.h) +list(APPEND ALL_HDRS ${PROJECT_GENERATED_HDRS}) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tmpl/libejdb2.pc.in + ${PROJECT_GENERATED_DIR}/libejdb2.pc @ONLY) + +if(DO_INSTALL_CORE) + install(FILES ${PROJECT_GENERATED_DIR}/libejdb2.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +endif() + +list(REMOVE_DUPLICATES PROJECT_LLIBRARIES) +list(REMOVE_DUPLICATES PROJECT_INCLUDE_DIRS) +include_directories(${PROJECT_INCLUDE_DIRS}) + +foreach(MODULE IN LISTS MODULES) + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}/CMakeLists.txt) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}) + endif() + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}/tools/CMakeLists.txt) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}/tools) + endif() + if(BUILD_TESTS AND EXISTS + ${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}/tests/CMakeLists.txt) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}/tests) + endif() + if(BUILD_EXAMPLES + AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}/examples/CMakeLists.txt) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}/examples) + endif() + if(BUILD_BENCHMARKS + AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}/benchmark/CMakeLists.txt) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${MODULE}/benchmark) + endif() +endforeach(MODULE) + +if(ENABLE_HTTP) + add_subdirectory(jbs) +endif() + +if(BUILD_TESTS) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/tests) +endif() + +if(BUILD_EXAMPLES) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/examples) +endif() + +if(BUILD_DART_BINDING) + add_subdirectory(bindings/ejdb2_dart) +endif() + +if(BUILD_JNI_BINDING) + add_subdirectory(bindings/ejdb2_jni) +endif() + +if(BUILD_NODEJS_BINDING) + add_subdirectory(bindings/ejdb2_node) +endif() + +if(BUILD_ANDROID_LIBS) + add_subdirectory(bindings/ejdb2_android) +endif() + +if(BUILD_REACT_NATIVE_BINDING) + add_subdirectory(bindings/ejdb2_react_native) +endif() + +if(BUILD_FLUTTER_BINDING) + add_subdirectory(bindings/ejdb2_flutter) +endif() + +if(BUILD_SWIFT_BINDING) + add_subdirectory(bindings/ejdb2_swift) +endif() + +if(NOT BUILD_SHARED_LIBS) + add_definitions(-DIW_NODLL) + add_library(ejdb2 STATIC ${ALL_SRC}) + add_library(ejdb2_s ALIAS ejdb2) +else() + add_library(ejdb2 SHARED ${ALL_SRC}) + add_library(ejdb2_s STATIC ${ALL_SRC}) +endif() + +target_link_libraries(ejdb2 ${PROJECT_LLIBRARIES}) +if(BUILD_SHARED_LIBS) + target_link_libraries(ejdb2_s ${PROJECT_LLIBRARIES}) +endif() + +if(BUILD_SHARED_LIBS) + if(WIN32) + add_dependencies(ejdb2 wintools_init) + set_target_properties(ejdb2 PROPERTIES LINK_FLAGS + "-Wl,--output-def,libejdb2.def") + add_w32_importlib(ejdb2 libejdb2 ${CMAKE_CURRENT_BINARY_DIR}) + + if(DO_INSTALL_CORE) + install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/libejdb2.def + ${CMAKE_CURRENT_BINARY_DIR}/libejdb2.lib + ${CMAKE_CURRENT_BINARY_DIR}/libejdb2.exp + DESTINATION ${CMAKE_INSTALL_LIBDIR}) + endif() + + endif() + set_target_properties( + ejdb2 + PROPERTIES VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + PUBLIC_HEADER "${PUB_HDRS}" + DEFINE_SYMBOL IW_API_EXPORTS) + + if(CMAKE_BUILD_TYPE STREQUAL "Release") + add_custom_command( + TARGET ejdb2 + POST_BUILD + COMMAND strip -s $) + endif() + + set_target_properties( + ejdb2_s PROPERTIES VERSION ${PROJECT_VERSION} COMPILE_FLAGS "-DIW_NODLL" + OUTPUT_NAME ejdb2-${PROJECT_VERSION_MAJOR}) +else() + set_target_properties( + ejdb2 + PROPERTIES VERSION ${PROJECT_VERSION} + PUBLIC_HEADER "${PUB_HDRS}" + COMPILE_FLAGS "-DIW_NODLL" + LINK_FLAGS_RELEASE "${LINK_FLAGS_RELEASE} -s" + OUTPUT_NAME ejdb2-${PROJECT_VERSION_MAJOR}) +endif() + +if(DO_INSTALL_CORE) + install( + TARGETS ejdb2 + EXPORT ejdb2-exports + FRAMEWORK DESTINATION ${FRAMEWORK_INSTALL_DIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) + install(EXPORT ejdb2-exports + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}) +endif() + +if(BUILD_SHARED_LIBS AND DO_INSTALL_CORE) + install( + TARGETS ejdb2_s + EXPORT ejdb2-static-exports + FRAMEWORK DESTINATION ${FRAMEWORK_INSTALL_DIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) + install(EXPORT ejdb2-static-exports + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}) +endif() + +if(DO_INSTALL_CORE) + # Install iowow headers included into ejdb2 + install( + DIRECTORY ${IOWOW_INCLUDE_DIR}/${PROJECT_NAME}/iowow + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} + COMPONENT headers + FILES_MATCHING + PATTERN "*.h") + install(FILES ${CMAKE_SOURCE_DIR}/LICENSE ${CMAKE_SOURCE_DIR}/Changelog + DESTINATION ${CMAKE_INSTALL_DOCDIR}) + install( + FILES ${CMAKE_SOURCE_DIR}/README.md + RENAME README + DESTINATION ${CMAKE_INSTALL_DOCDIR}) + + export(EXPORT ejdb2-exports) + if(BUILD_SHARED_LIBS) + export(EXPORT ejdb2-static-exports) + endif(BUILD_SHARED_LIBS) + +endif() + +include(InstallRequiredSystemLibraries) + +set(${PROJECT_NAME}_PUB_HDRS + ${PUB_HDRS} + CACHE INTERNAL "${PROJECT_NAME}: Public headers" FORCE) +set(${PROJECT_NAME}_INCLUDE_DIRS + ${PROJECT_INCLUDE_DIRS} + CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE) + +message("") +message("CMAKE_GENERATOR: ${CMAKE_GENERATOR}") +message("${PROJECT_NAME} LINK LIBS: ${PROJECT_LLIBRARIES}") +message("${PROJECT_NAME} ASAN: ${ASAN}") +message("\n${PROJECT_NAME} INCLUDE DIRS: ${PROJECT_INCLUDE_DIRS}") +message("\n${PROJECT_NAME} SOURCES: ${ALL_SRC}") +message("\n${PROJECT_NAME} PUB_HDRS: ${PUB_HDRS}") +message("\n${PROJECT_NAME} CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") +message("${PROJECT_NAME} ANDROID_ABIS: ${ANDROID_ABIS}") +message("${PROJECT_NAME} ENABLE_HTTP: ${ENABLE_HTTP}") +message("${PROJECT_NAME} BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}") +message("${PROJECT_NAME} BUILD_TESTS: ${BUILD_TESTS}") +message("${PROJECT_NAME} BUILD_EXAMPLES: ${BUILD_EXAMPLES}") +message("${PROJECT_NAME} BUILD_BENCHMARKS: ${BUILD_BENCHMARKS}") +message("${PROJECT_NAME} BUILD_DART_BINDING: ${BUILD_DART_BINDING}") +message("${PROJECT_NAME} BUILD_ANDROID_LIBS: ${BUILD_ANDROID_LIBS}") +message("${PROJECT_NAME} BUILD_JNI_BINDING: ${BUILD_JNI_BINDING}") +message("${PROJECT_NAME} BUILD_NODEJS_BINDING: ${BUILD_NODEJS_BINDING}") +message( + "${PROJECT_NAME} BUILD_REACT_NATIVE_BINDING: ${BUILD_REACT_NATIVE_BINDING}") +message("${PROJECT_NAME} BUILD_FLUTTER_BINDING: ${BUILD_FLUTTER_BINDING}") +message("${PROJECT_NAME} BUILD_SWIFT_BINDING: ${BUILD_SWIFT_BINDING}") diff --git a/src/bindings/ejdb2_android/CMakeLists.txt b/src/bindings/ejdb2_android/CMakeLists.txt new file mode 100644 index 0000000..6d724ca --- /dev/null +++ b/src/bindings/ejdb2_android/CMakeLists.txt @@ -0,0 +1,36 @@ +if (NOT DEFINED ANDROID_NDK_HOME) + if (DEFINED ENV{ANDROID_NDK_HOME}) + set(ANDROID_NDK_HOME $ENV{ANDROID_NDK_HOME}) + else () + message(FATAL_ERROR "Missing required ANDROID_NDK_HOME option/env. Use -DANDROID_NDK_HOME=...") + endif () +endif () + +set(ANDROID_BUILD_DIR "${CMAKE_SOURCE_DIR}/build_android") +set(ANDROID_LIBS_DIR "${CMAKE_CURRENT_BINARY_DIR}/libs") + +foreach (AABI IN ITEMS ${ANDROID_ABIS}) + list(APPEND ANDROID_ABIS_LIBS "${ANDROID_LIBS_DIR}/${AABI}/libejdb2jni.so") + add_custom_target( + android_${AABI} ALL + BYPRODUCTS ${ANDROID_LIBS_DIR}/${AABI}/libejdb2jni.so + COMMAND ${CMAKE_COMMAND} -E remove_directory ${ANDROID_BUILD_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory ${ANDROID_BUILD_DIR} + COMMAND ${CMAKE_COMMAND} -G ${CMAKE_GENERATOR} -S ${CMAKE_SOURCE_DIR} + -B ${ANDROID_BUILD_DIR} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DBUILD_JNI_BINDING=ON + -DENABLE_HTTP=OFF + -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake + -DANDROID_ABI=${AABI} + -DANDROID_PLATFORM=android-21 + -DANDROID_NATIVE_API_LEVEL=21 + -DIOWOW_URL=${IOWOW_URL} + COMMAND ${CMAKE_COMMAND} --build ${ANDROID_BUILD_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory ${ANDROID_LIBS_DIR}/${AABI} + COMMAND ${CMAKE_COMMAND} -E copy ${ANDROID_BUILD_DIR}/src/bindings/ejdb2_jni/src/libejdb2jni.so ${ANDROID_LIBS_DIR}/${AABI} + COMMAND ${CMAKE_COMMAND} -E copy ${ANDROID_BUILD_DIR}/src/bindings/ejdb2_jni/src/ejdb2.jar ${ANDROID_LIBS_DIR} + COMMAND ${CMAKE_COMMAND} -E remove_directory ${ANDROID_BUILD_DIR} + VERBATIM + ) +endforeach () \ No newline at end of file diff --git a/src/bindings/ejdb2_android/README.md b/src/bindings/ejdb2_android/README.md new file mode 100644 index 0000000..33ed493 --- /dev/null +++ b/src/bindings/ejdb2_android/README.md @@ -0,0 +1,10 @@ +# Android + +* [Flutter binding](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_flutter) +* [React Native binding](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_react_native) + +## Sample Android application + +* https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_android/test + +* https://github.com/Softmotions/ejdb_android_todo_app \ No newline at end of file diff --git a/src/bindings/ejdb2_android/test/.gitignore b/src/bindings/ejdb2_android/test/.gitignore new file mode 100644 index 0000000..5cbce53 --- /dev/null +++ b/src/bindings/ejdb2_android/test/.gitignore @@ -0,0 +1,6 @@ +!/gradle/wrapper/gradle-wrapper.jar +.gradle +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/src/bindings/ejdb2_android/test/README.md b/src/bindings/ejdb2_android/test/README.md new file mode 100644 index 0000000..523aab9 --- /dev/null +++ b/src/bindings/ejdb2_android/test/README.md @@ -0,0 +1,30 @@ +# Android + +## Sample Android application + +https://github.com/Softmotions/ejdb_android_todo_app + +## Android binding showcase and unit tests + +```bash +cd ./src/bindings/ejdb2_android/test +``` + +Set local android SDK/NDK path and target `arch` in `local.properties` + +```properties +# Path to Android SDK dir +sdk.dir=/Android-sdk + +# Path to Android NDK dir +ndk.dir=/Android-sdk/ndk-bundle + +# Target abi name: armeabi-v7a, arm64-v8a, x86, x86_64 +abi.name=arm64-v8a +``` + +Run Android emulator for the same abi version then: + +```bash +./gradlew connectedAndroidTest +``` \ No newline at end of file diff --git a/src/bindings/ejdb2_android/test/build.gradle b/src/bindings/ejdb2_android/test/build.gradle new file mode 100644 index 0000000..5291ec5 --- /dev/null +++ b/src/bindings/ejdb2_android/test/build.gradle @@ -0,0 +1,36 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath "com.android.tools.build:gradle:3.1.0" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} + +Properties properties = new Properties() +if (project.rootProject.file('local.properties').exists()) { + def is = project.rootProject.file('local.properties').newDataInputStream() + properties.load(is) + is.close() +} + +ext { + abiName = properties.get("abi.name") +} \ No newline at end of file diff --git a/src/bindings/ejdb2_android/test/ejdb2/.gitignore b/src/bindings/ejdb2_android/test/ejdb2/.gitignore new file mode 100644 index 0000000..f11a73c --- /dev/null +++ b/src/bindings/ejdb2_android/test/ejdb2/.gitignore @@ -0,0 +1,3 @@ +/build +/jniLibs +/libs \ No newline at end of file diff --git a/src/bindings/ejdb2_android/test/ejdb2/build.gradle b/src/bindings/ejdb2_android/test/ejdb2/build.gradle new file mode 100644 index 0000000..647587a --- /dev/null +++ b/src/bindings/ejdb2_android/test/ejdb2/build.gradle @@ -0,0 +1,65 @@ +apply plugin: 'com.android.library' + +def props = rootProject.ext + +android { + compileSdkVersion 21 + defaultConfig { + minSdkVersion 21 + targetSdkVersion 21 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + abiFilters props.abiName + targets "ejdb2jni" + arguments "-DCMAKE_BUILD_TYPE=Release", + "-DBUILD_JNI_BINDING=ON", + "-DENABLE_HTTP=OFF", + "-DBUILD_EXAMPLES=OFF", + "-DCMAKE_TOOLCHAIN_FILE=$ndkDirectory/build/cmake/android.toolchain.cmake", + "-DANDROID_ABI=$props.abiName", + "-DANDROID_PLATFORM=android-21", + "-DANDROID_NATIVE_API_LEVEL=21" + } + } + } + externalNativeBuild { + cmake { + path file("../../../../CMakeLists.txt") + } + } +} + +project.afterEvaluate { + if (tasks.findByName("externalNativeBuildDebug")) { + javaPreCompileDebug.dependsOn externalNativeBuildDebug + externalNativeBuildDebug.doLast { + copy { + from(".externalNativeBuild/cmake/debug/$props.abiName/src/bindings/ejdb2_jni/src") { + include "ejdb2.jar" + } + into "libs" + } + } + } + if (tasks.findByName("externalNativeBuildRelease")) { + javaPreCompileRelease.dependsOn externalNativeBuildRelease + externalNativeBuildRelease.doLast { + copy { + from(".externalNativeBuild/cmake/release/$props.abiName/src/bindings/ejdb2_jni/src") { + include "ejdb2.jar" + } + into "libs" + } + } + } +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) + testImplementation "junit:junit:4.12" + androidTestImplementation "com.android.support.test:runner:1.0.2" + androidTestImplementation "com.android.support.test.espresso:espresso-core:3.0.2" +} \ No newline at end of file diff --git a/src/bindings/ejdb2_android/test/ejdb2/src/androidTest/java/com/softmotions/ejdb2/Ejdb2AndroidTest.java b/src/bindings/ejdb2_android/test/ejdb2/src/androidTest/java/com/softmotions/ejdb2/Ejdb2AndroidTest.java new file mode 100644 index 0000000..3c63298 --- /dev/null +++ b/src/bindings/ejdb2_android/test/ejdb2/src/androidTest/java/com/softmotions/ejdb2/Ejdb2AndroidTest.java @@ -0,0 +1,189 @@ +package com.softmotions.ejdb2; + +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +@RunWith(AndroidJUnit4.class) +public class Ejdb2AndroidTest { + + private EJDB2 db; + + @Before + public void start() { + try { + File dir = InstrumentationRegistry.getTargetContext().getCacheDir(); + File path = new File(dir, "test.db"); + db = new EJDB2Builder(path.getAbsolutePath()).truncate().open(); + } catch (EJDB2Exception e) { + e.printStackTrace(); + fail(); + } + } + + @After + public void shutdown() { + db.close(); + } + + @Test + public void ejdb2tests() { + try { + EJDB2Exception exception; + String json = "{'foo':'bar'}".replace('\'', '"'); + String patch = "[{'op':'add', 'path':'/baz', 'value':'qux'}]".replace('\'', '"'); + + long id = db.put("c1", json); + assertEquals(1L, id); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + db.get("c1", 1L, bos); + assertEquals(bos.toString(), json); + + db.patch("c1", patch, id); + bos.reset(); + db.get("c1", 1L, bos); + assertEquals(bos.toString(), "{'foo':'bar','baz':'qux'}".replace('\'', '"')); + bos.reset(); + + db.del("c1", id); + + exception = null; + try { + db.get("c1", id, bos); + } catch (EJDB2Exception e) { + exception = e; + } + assertNotNull(exception); + assertTrue(exception.getMessage().contains("IWKV_ERROR_NOTFOUND")); + + // JQL resources can be closed explicitly or garbage collected + JQL q = db.createQuery("@mycoll/*"); + assertNotNull(q.getDb()); + assertEquals(q.getCollection(), "mycoll"); + + id = db.put("mycoll", "{'foo':'bar'}".replace('\'', '"')); + assertEquals(1, id); + + exception = null; + try { + db.put("mycoll", "{\""); + } catch (EJDB2Exception e) { + exception = e; + } + assertTrue(exception != null && exception.getMessage() != null); + assertEquals(86005, exception.getCode()); + assertTrue(exception.getMessage().contains("JBL_ERROR_PARSE_UNQUOTED_STRING")); + + db.put("mycoll", "{'foo':'baz'}".replace('\'', '"')); + + final Map results = new LinkedHashMap<>(); + q.execute(new JQLCallback() { + @Override + public long onRecord(long docId, String doc) { + assertTrue(docId > 0 && doc != null); + results.put(docId, doc); + return 1; + } + }); + assertEquals(2, results.size()); + assertEquals(results.get(1L), "{\"foo\":\"bar\"}"); + assertEquals(results.get(2L), "{\"foo\":\"baz\"}"); + results.clear(); + + try { + JQL q2 = db.createQuery("/[foo=:?]", "mycoll").setString(0, "zaz"); + q2.execute(new JQLCallback() { + @Override + public long onRecord(long docId, String doc) { + results.put(docId, doc); + return 1; + } + }); + q2.close(); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + assertTrue(results.isEmpty()); + + try { + JQL q2 = db.createQuery("/[foo=:val]", "mycoll").setString("val", "bar"); + q2.execute(new JQLCallback() { + @Override + public long onRecord(long docId, String doc) { + results.put(docId, doc); + return 1; + } + }); + q2.close(); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + assertEquals(1, results.size()); + assertEquals(results.get(1L), "{\"foo\":\"bar\"}"); + + exception = null; + try { + db.createQuery("@mycoll/["); + } catch (EJDB2Exception e) { + exception = e; + } + assertTrue(exception != null && exception.getMessage() != null); + assertEquals(87001, exception.getCode()); + assertTrue(exception.getMessage().contains("@mycoll/[ <---")); + + + long count = db.createQuery("@mycoll/* | count").executeScalarInt(); + assertEquals(2, count); + + q.withExplain().execute(); + assertTrue(q.getExplainLog().contains("[INDEX] NO [COLLECTOR] PLAIN")); + + json = db.infoAsString(); + assertNotNull(json); + assertTrue(json.contains("{\"name\":\"mycoll\",\"dbid\":4,\"rnum\":2,\"indexes\":[]}")); + + // Indexes + db.ensureStringIndex("mycoll", "/foo", true); + json = db.infoAsString(); + assertTrue(json.contains("\"indexes\":[{\"ptr\":\"/foo\",\"mode\":5,\"idbf\":0,\"dbid\":5,\"rnum\":2}]")); + + + db.patch("mycoll", patch, 2); + + json = db.createQuery("@mycoll/[foo=:?] and /[baz=:?]") + .setString(0, "baz") + .setString(1, "qux") + .firstJson(); + assertEquals("{\"foo\":\"baz\",\"baz\":\"qux\"}", json); + + json = db.createQuery("@mycoll/[foo re :?]").setRegexp(0, ".*").firstJson(); + assertEquals("{\"foo\":\"baz\",\"baz\":\"qux\"}", json); + + db.removeStringIndex("mycoll", "/foo", true); + json = db.infoAsString(); + assertTrue(json.contains("{\"name\":\"mycoll\",\"dbid\":4,\"rnum\":2,\"indexes\":[]}")); + + db.removeCollection("mycoll"); + db.removeCollection("c1"); + json = db.infoAsString(); + assertTrue(json.contains("\"collections\":[]")); + } catch (UnsupportedEncodingException | EJDB2Exception e) { + e.printStackTrace(); + fail(); + } + } +} diff --git a/src/bindings/ejdb2_android/test/ejdb2/src/main/AndroidManifest.xml b/src/bindings/ejdb2_android/test/ejdb2/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c8aa4d8 --- /dev/null +++ b/src/bindings/ejdb2_android/test/ejdb2/src/main/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/src/bindings/ejdb2_android/test/gradle.properties b/src/bindings/ejdb2_android/test/gradle.properties new file mode 100644 index 0000000..82618ce --- /dev/null +++ b/src/bindings/ejdb2_android/test/gradle.properties @@ -0,0 +1,15 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + + diff --git a/src/bindings/ejdb2_android/test/gradle/wrapper/gradle-wrapper.jar b/src/bindings/ejdb2_android/test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5c2d1cf Binary files /dev/null and b/src/bindings/ejdb2_android/test/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src/bindings/ejdb2_android/test/gradle/wrapper/gradle-wrapper.properties b/src/bindings/ejdb2_android/test/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..f4d7b2b --- /dev/null +++ b/src/bindings/ejdb2_android/test/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/src/bindings/ejdb2_android/test/gradlew b/src/bindings/ejdb2_android/test/gradlew new file mode 100755 index 0000000..b0d6d0a --- /dev/null +++ b/src/bindings/ejdb2_android/test/gradlew @@ -0,0 +1,188 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/src/bindings/ejdb2_android/test/gradlew.bat b/src/bindings/ejdb2_android/test/gradlew.bat new file mode 100644 index 0000000..9991c50 --- /dev/null +++ b/src/bindings/ejdb2_android/test/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/bindings/ejdb2_android/test/local.properties b/src/bindings/ejdb2_android/test/local.properties new file mode 100644 index 0000000..23489b9 --- /dev/null +++ b/src/bindings/ejdb2_android/test/local.properties @@ -0,0 +1,9 @@ + +## Path to Android SDK dir +sdk.dir=/Android-sdk + +## Path to Android NDK dir +ndk.dir=/Android-sdk/ndk-bundle + +## Target abi name: armeabi-v7a, arm64-v8a, x86, x86_64 +abi.name=x86 \ No newline at end of file diff --git a/src/bindings/ejdb2_android/test/settings.gradle b/src/bindings/ejdb2_android/test/settings.gradle new file mode 100644 index 0000000..c0509d9 --- /dev/null +++ b/src/bindings/ejdb2_android/test/settings.gradle @@ -0,0 +1 @@ +include ':ejdb2' diff --git a/src/bindings/ejdb2_dart/.gitignore b/src/bindings/ejdb2_dart/.gitignore new file mode 100644 index 0000000..13bb388 --- /dev/null +++ b/src/bindings/ejdb2_dart/.gitignore @@ -0,0 +1 @@ +!/.packages \ No newline at end of file diff --git a/src/bindings/ejdb2_dart/.packages b/src/bindings/ejdb2_dart/.packages new file mode 100644 index 0000000..7d676ba --- /dev/null +++ b/src/bindings/ejdb2_dart/.packages @@ -0,0 +1,54 @@ +# Generated by pub on 2020-12-21 22:58:24.542808. +_fe_analyzer_shared:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/_fe_analyzer_shared-7.0.0/lib/ +analyzer:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/analyzer-0.39.17/lib/ +args:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/args-1.6.0/lib/ +async:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/async-2.5.0-nullsafety.3/lib/ +boolean_selector:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0-nullsafety.3/lib/ +charcode:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/charcode-1.2.0-nullsafety.3/lib/ +cli_util:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/cli_util-0.2.0/lib/ +collection:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/collection-1.15.0-nullsafety.5/lib/ +convert:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/convert-2.1.1/lib/ +coverage:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/coverage-0.14.0/lib/ +crypto:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/crypto-2.1.5/lib/ +csslib:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/csslib-0.16.2/lib/ +glob:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/glob-1.2.0/lib/ +html:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/html-0.14.0+3/lib/ +http_multi_server:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/http_multi_server-2.2.0/lib/ +http_parser:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/http_parser-3.1.4/lib/ +io:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/io-0.3.4/lib/ +js:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/js-0.6.3-nullsafety.3/lib/ +json_at:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/json_at-2.0.0-nullsafety.1/lib/ +logging:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/logging-0.11.4/lib/ +matcher:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.10-nullsafety.3/lib/ +meta:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/meta-1.3.0-nullsafety.6/lib/ +mime:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/mime-0.9.7/lib/ +node_interop:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/node_interop-1.1.1/lib/ +node_io:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/node_io-1.1.1/lib/ +node_preamble:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/node_preamble-1.4.12/lib/ +package_config:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/package_config-1.9.3/lib/ +path:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/path-1.8.0-nullsafety.3/lib/ +pedantic:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/pedantic-1.10.0-nullsafety.3/lib/ +pool:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/pool-1.5.0-nullsafety.3/lib/ +pub_semver:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/pub_semver-1.4.4/lib/ +quiver:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/quiver-3.0.0-nullsafety.2/lib/ +shelf:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/shelf-0.7.9/lib/ +shelf_packages_handler:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/shelf_packages_handler-2.0.0/lib/ +shelf_static:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/shelf_static-0.2.8/lib/ +shelf_web_socket:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/shelf_web_socket-0.2.3/lib/ +source_map_stack_trace:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/source_map_stack_trace-2.1.0-nullsafety.4/lib/ +source_maps:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/source_maps-0.10.10-nullsafety.3/lib/ +source_span:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.0-nullsafety.4/lib/ +stack_trace:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0-nullsafety.6/lib/ +stream_channel:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0-nullsafety.3/lib/ +string_scanner:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0-nullsafety.3/lib/ +term_glyph:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0-nullsafety.3/lib/ +test:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/test-1.16.0-nullsafety.13/lib/ +test_api:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/test_api-0.2.19-nullsafety.6/lib/ +test_core:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/test_core-0.3.12-nullsafety.12/lib/ +typed_data:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0-nullsafety.5/lib/ +vm_service:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/vm_service-4.2.0/lib/ +watcher:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/watcher-0.9.7+15/lib/ +web_socket_channel:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/web_socket_channel-1.1.0/lib/ +webkit_inspection_protocol:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/webkit_inspection_protocol-0.7.3/lib/ +yaml:file:///home/adam/.pub-cache/hosted/pub.dartlang.org/yaml-2.2.1/lib/ +ejdb2_dart:lib/ diff --git a/src/bindings/ejdb2_dart/CHANGELOG.md b/src/bindings/ejdb2_dart/CHANGELOG.md new file mode 100644 index 0000000..6dd20db --- /dev/null +++ b/src/bindings/ejdb2_dart/CHANGELOG.md @@ -0,0 +1,4 @@ +ejdb_dart (@EJDB2_DART_VERSION@) + +- Upgraded to Dart v2.12.x (null safety and native bindings API) +- Used new versioning scheme: {EJDB_VERSION}{BINDING_VERSION_NUMBER} \ No newline at end of file diff --git a/src/bindings/ejdb2_dart/CMakeLists.txt b/src/bindings/ejdb2_dart/CMakeLists.txt new file mode 100644 index 0000000..af690d7 --- /dev/null +++ b/src/bindings/ejdb2_dart/CMakeLists.txt @@ -0,0 +1,91 @@ +# Build dart binding shared library + +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/version.txt _VERSION) +set_property(GLOBAL PROPERTY EJDB2_DART_VERSION_PROPERTY + "${PROJECT_VERSION}${_VERSION}") +set(EJDB2_DART_VERSION "${PROJECT_VERSION}${_VERSION}") + +add_library(ejdb2_dart SHARED lib/ejdb2_dart.c) +target_link_libraries(ejdb2_dart ejdb2_s ${PROJECT_LLIBRARIES}) + +set(dart_COMPILE_FLAGS " ") +set(dart_LINK_FLAGS " ") + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + set(dart_LINK_FLAGS "-dynamic -undefined dynamic_lookup") +endif() + +set(DART_PUB_DIR ${CMAKE_CURRENT_BINARY_DIR}/ejdb2_dart) + +configure_file(lib/ejdb2_dart.c ${DART_PUB_DIR}/lib/ejdb2_dart.c COPYONLY) +configure_file(lib/ejdb2_dart.dart ${DART_PUB_DIR}/lib/ejdb2_dart.dart COPYONLY) +configure_file(test/ejdb2_dart_test.dart + ${DART_PUB_DIR}/test/ejdb2_dart_test.dart COPYONLY) +configure_file(example/example.dart ${DART_PUB_DIR}/example/example.dart + COPYONLY) +configure_file(example/isolate.dart ${DART_PUB_DIR}/example/isolate.dart + COPYONLY) + +configure_file(README.md ${DART_PUB_DIR}/README.md COPYONLY) +configure_file(LICENSE ${DART_PUB_DIR}/LICENSE COPYONLY) +configure_file(CHANGELOG.md ${DART_PUB_DIR}/CHANGELOG.md @ONLY) +configure_file(pubspec.yaml.in ${DART_PUB_DIR}/pubspec.yaml @ONLY) +configure_file(analysis_options.yaml ${DART_PUB_DIR}/analysis_options.yaml + COPYONLY) +configure_file(.packages ${DART_PUB_DIR}/.packages COPYONLY) + +set_target_properties( + ejdb2_dart + PROPERTIES COMPILE_FLAGS ${dart_COMPILE_FLAGS} LINK_FLAGS ${dart_LINK_FLAGS} + LIBRARY_OUTPUT_DIRECTORY ${DART_PUB_DIR}/lib) + +if(CMAKE_BUILD_TYPE STREQUAL "Release") + add_custom_command( + TARGET ejdb2_dart + POST_BUILD + COMMAND strip -s $) +endif() + +set_target_properties( + ejdb2_dart + PROPERTIES VERSION ${PROJECT_VERSION} OUTPUT_NAME ejdb2dart + SOVERSION ${PROJECT_VERSION_MAJOR} DEFINE_SYMBOL IW_API_EXPORTS) + +install( + TARGETS ejdb2_dart + FRAMEWORK DESTINATION ${FRAMEWORK_INSTALL_DIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) + +install(FILES ${DART_PUB_DIR}/lib/ejdb2_dart.dart ${DART_PUB_DIR}/README.md + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/dart) + +install(FILES ${DART_PUB_DIR}/example/example.dart + ${DART_PUB_DIR}/example/isolate.dart + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/dart/example) + +if(BUILD_TESTS) + find_program(DART_EXEC dart HINTS /usr/bin /usr/local/bin) + if(DART_EXEC MATCHES "DART_EXEC-NOTFOUND") + message(FATAL_ERROR "`dart` executable not found") + endif() + find_program(PUB_EXEC pub /usr/bin /usr/local/bin) + if(PUB_EXEC MATCHES "PUB_EXEC-NOTFOUND") + set(PUB_EXEC ${DART_EXEC} pub) + endif() + + add_custom_command( + COMMAND ${PUB_EXEC} get + OUTPUT ${DART_PUB_DIR}/pubspec.lock + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/pubspec.yaml + WORKING_DIRECTORY ${DART_PUB_DIR} + VERBATIM) + add_custom_target(pub_get ALL DEPENDS ${DART_PUB_DIR}/pubspec.lock) + + add_test( + NAME ejdb2dart + COMMAND ${DART_EXEC} --enable-asserts ./test/ejdb2_dart_test.dart + WORKING_DIRECTORY ${DART_PUB_DIR}) +endif(BUILD_TESTS) diff --git a/src/bindings/ejdb2_dart/LICENSE b/src/bindings/ejdb2_dart/LICENSE new file mode 100644 index 0000000..5319d1e --- /dev/null +++ b/src/bindings/ejdb2_dart/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2012-2021 Softmotions Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/bindings/ejdb2_dart/README.md b/src/bindings/ejdb2_dart/README.md new file mode 100644 index 0000000..0a79fe3 --- /dev/null +++ b/src/bindings/ejdb2_dart/README.md @@ -0,0 +1,49 @@ +# EJDB2 Dart VM native binding + +Embeddable JSON Database engine http://ejdb.org Dart binding. + +See https://github.com/Softmotions/ejdb/blob/master/README.md + +For API usage examples take a look into [/example](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_dart/example) and [/test](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_dart/test) folders. + +## Example + +```dart +import 'package:ejdb2_dart/ejdb2_dart.dart'; + +void main() async { + final db = await EJDB2.open('example.db', truncate: true); + + var id = await db.put('parrots', {'name': 'Bianca', 'age': 4}); + print('Bianca record: ${id}'); + + id = await db.put('parrots', {'name': 'Darko', 'age': 8}); + print('Darko record: ${id}'); + + final q = db.createQuery('/[age > :age]', 'parrots'); + await for (final doc in q.setInt('age', 3).execute()) { + print('Found ${doc}'); + } + await db.close(); +} +``` + +## Supported platforms + +* Linux x64 +* OSX + +## How build it manually + +Note: native binding requires Dart SDK version >= 2.12.x + +``` sh +git clone https://github.com/Softmotions/ejdb.git +cd ./ejdb +mkdir ./build && cd build +cmake .. -DBUILD_DART_BINDING=ON -DCMAKE_BUILD_TYPE=Release +make +cd src/bindings/ejdb2_dart/ejdb2_dart +pub get +``` + diff --git a/src/bindings/ejdb2_dart/analysis_options.yaml b/src/bindings/ejdb2_dart/analysis_options.yaml new file mode 100644 index 0000000..8f52b68 --- /dev/null +++ b/src/bindings/ejdb2_dart/analysis_options.yaml @@ -0,0 +1,157 @@ +analyzer: + errors: + native_function_body_in_non_sdk_code: ignore + strong-mode: + implicit-casts: false + implicit-dynamic: true +linter: + rules: + - always_declare_return_types + - always_put_required_named_parameters_first + - always_require_non_null_named_parameters + - annotate_overrides + - avoid_bool_literals_in_conditional_expressions + - avoid_catching_errors + - avoid_classes_with_only_static_members + - avoid_double_and_int_checks + - avoid_empty_else + - avoid_field_initializers_in_const_classes + - avoid_implementing_value_types + - avoid_init_to_null + - avoid_js_rounded_ints + - avoid_null_checks_in_equality_operators + - avoid_private_typedef_functions + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + - avoid_returning_null + - avoid_returning_null_for_future + - avoid_returning_null_for_void + #- avoid_returning_this + - avoid_setters_without_getters + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + - avoid_types_as_parameter_names + - avoid_unused_constructor_parameters + - camel_case_extensions + - camel_case_types + - cancel_subscriptions + - comment_references + - control_flow_in_finally + - curly_braces_in_flow_control_structures + - diagnostic_describe_all_properties + - directives_ordering + - empty_catches + - empty_constructor_bodies + - empty_statements + - file_names + - flutter_style_todos + - hash_and_equals + - implementation_imports + - invariant_booleans + - iterable_contains_unrelated_type + - join_return_with_assignment + - library_names + - list_remove_unrelated_type + - literal_only_boolean_expressions + - no_adjacent_strings_in_list + - no_duplicate_case_values + - null_closures + - one_member_abstracts + - only_throw_errors + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + #- prefer_asserts_with_message + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_contains + - prefer_equal_for_default_values + # - prefer_expression_function_bodies + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_for_elements_to_map_fromIterable + - prefer_foreach + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_iterable_whereType + - prefer_mixin + - prefer_null_aware_operators + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - prefer_void_to_null + - provide_deprecation_message + - recursive_getters + - slash_for_doc_comments + - sort_child_properties_last + - sort_constructors_first + - sort_pub_dependencies + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - type_init_formals + - unawaited_futures + - unnecessary_await_in_return + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_statements + - unnecessary_this + - unrelated_type_equality_checks + - unsafe_html + - use_full_hex_values_for_flutter_colors + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_string_buffers + - use_to_and_as_if_applicable + - valid_regexps + - void_checks + # - always_put_control_body_on_new_line + # - always_specify_types + # - avoid_annotating_with_dynamic + # - avoid_as + # - avoid_catches_without_on_clauses + # - avoid_equals_and_hash_code_on_mutable_classes + # - avoid_function_literals_in_foreach_calls + # - avoid_positional_boolean_parameters + # - avoid_print + # - avoid_types_on_closure_parameters + # - avoid_void_async + # - await_only_futures + # - cascade_invocations + # - close_sinks + # - constant_identifier_names + # - library_prefixes + # - lines_longer_than_80_chars + # - non_constant_identifier_names + # - omit_local_variable_types + # - parameter_assignments + # - prefer_const_constructors_in_immutables + # - prefer_double_quotes + # - prefer_if_elements_to_conditional_expressions + # - prefer_int_literals + # - public_member_api_docs + # - type_annotate_public_apis + # - unnecessary_brace_in_string_interps + # - use_function_type_syntax_for_parameters diff --git a/src/bindings/ejdb2_dart/example/example.dart b/src/bindings/ejdb2_dart/example/example.dart new file mode 100644 index 0000000..95eceee --- /dev/null +++ b/src/bindings/ejdb2_dart/example/example.dart @@ -0,0 +1,17 @@ +import 'package:ejdb2_dart/ejdb2_dart.dart'; + +void main() async { + final db = await EJDB2.open('example.db', truncate: true); + + var id = await db.put('parrots', {'name': 'Bianca', 'age': 4}); + print('Bianca record: ${id}'); + + id = await db.put('parrots', {'name': 'Darko', 'age': 8}); + print('Darko record: ${id}'); + + final q = db.createQuery('/[age > :age]', 'parrots'); + await for (final doc in q.setInt('age', 3).execute()) { + print('Found $doc'); + } + await db.close(); +} diff --git a/src/bindings/ejdb2_dart/example/isolate.dart b/src/bindings/ejdb2_dart/example/isolate.dart new file mode 100644 index 0000000..50fbe99 --- /dev/null +++ b/src/bindings/ejdb2_dart/example/isolate.dart @@ -0,0 +1,38 @@ +import 'dart:async'; +import 'dart:isolate'; + +import 'package:ejdb2_dart/ejdb2_dart.dart'; + +/// Database access from multiple isolates. +/// Based on isolate example from https://github.com/adamlofts/leveldb_dart + +void main() async { + final runners = Iterable.generate(5).map((int index) { + return Runner.spawn(index); + }).toList(); + await Future.wait(runners.map((Runner r) => r.finish)); +} + +Future run(int index) async { + print('Thread ${index} write'); + final db = await EJDB2.open('isolate.db', truncate: true); + await db.put('c1', {'index': index}); + // Sleep 1 second + await Future.delayed(const Duration(seconds: 1)); + final nextKey = (index + 1) % 5; + final doc = await db.createQuery('/[index=:?]', 'c1').setInt(0, nextKey).execute().first; + print('Thread ${index} read: ${doc}'); +} + +class Runner { + final Completer _finish = Completer(); + final RawReceivePort _finishPort = RawReceivePort(); + Runner.spawn(int index) { + _finishPort.handler = (dynamic _) { + _finish.complete(); + _finishPort.close(); + }; + Isolate.spawn(run, index, onExit: _finishPort.sendPort); + } + Future get finish => _finish.future; +} diff --git a/src/bindings/ejdb2_dart/lib/dart_api.h b/src/bindings/ejdb2_dart/lib/dart_api.h new file mode 100644 index 0000000..189a6b7 --- /dev/null +++ b/src/bindings/ejdb2_dart/lib/dart_api.h @@ -0,0 +1,3944 @@ +/* + * Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#ifndef RUNTIME_INCLUDE_DART_API_H_ +#define RUNTIME_INCLUDE_DART_API_H_ + +/** \mainpage Dart Embedding API Reference + * + * This reference describes the Dart Embedding API, which is used to embed the + * Dart Virtual Machine within C/C++ applications. + * + * This reference is generated from the header include/dart_api.h. + */ + +/* __STDC_FORMAT_MACROS has to be defined before including to + * enable platform independent printf format specifiers. */ +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif + +#include +#include +#include + +#ifdef __cplusplus +#define DART_EXTERN_C extern "C" +#else +#define DART_EXTERN_C +#endif + +#if defined(__CYGWIN__) +#error Tool chain and platform not supported. +#elif defined(_WIN32) +#if defined(DART_SHARED_LIB) +#define DART_EXPORT DART_EXTERN_C __declspec(dllexport) +#else +#define DART_EXPORT DART_EXTERN_C +#endif +#else +#if __GNUC__ >= 4 +#if defined(DART_SHARED_LIB) +#define DART_EXPORT \ + DART_EXTERN_C __attribute__((visibility("default"))) __attribute((used)) +#else +#define DART_EXPORT DART_EXTERN_C +#endif +#else +#error Tool chain not supported. +#endif +#endif + +#if __GNUC__ +#define DART_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#elif _MSC_VER +#define DART_WARN_UNUSED_RESULT _Check_return_ +#else +#define DART_WARN_UNUSED_RESULT +#endif + +/* + * ======= + * Handles + * ======= + */ + +/** + * An isolate is the unit of concurrency in Dart. Each isolate has + * its own memory and thread of control. No state is shared between + * isolates. Instead, isolates communicate by message passing. + * + * Each thread keeps track of its current isolate, which is the + * isolate which is ready to execute on the current thread. The + * current isolate may be NULL, in which case no isolate is ready to + * execute. Most of the Dart apis require there to be a current + * isolate in order to function without error. The current isolate is + * set by any call to Dart_CreateIsolateGroup or Dart_EnterIsolate. + */ +typedef struct _Dart_Isolate* Dart_Isolate; +typedef struct _Dart_IsolateGroup* Dart_IsolateGroup; + +/** + * An object reference managed by the Dart VM garbage collector. + * + * Because the garbage collector may move objects, it is unsafe to + * refer to objects directly. Instead, we refer to objects through + * handles, which are known to the garbage collector and updated + * automatically when the object is moved. Handles should be passed + * by value (except in cases like out-parameters) and should never be + * allocated on the heap. + * + * Most functions in the Dart Embedding API return a handle. When a + * function completes normally, this will be a valid handle to an + * object in the Dart VM heap. This handle may represent the result of + * the operation or it may be a special valid handle used merely to + * indicate successful completion. Note that a valid handle may in + * some cases refer to the null object. + * + * --- Error handles --- + * + * When a function encounters a problem that prevents it from + * completing normally, it returns an error handle (See Dart_IsError). + * An error handle has an associated error message that gives more + * details about the problem (See Dart_GetError). + * + * There are four kinds of error handles that can be produced, + * depending on what goes wrong: + * + * - Api error handles are produced when an api function is misused. + * This happens when a Dart embedding api function is called with + * invalid arguments or in an invalid context. + * + * - Unhandled exception error handles are produced when, during the + * execution of Dart code, an exception is thrown but not caught. + * Prototypically this would occur during a call to Dart_Invoke, but + * it can occur in any function which triggers the execution of Dart + * code (for example, Dart_ToString). + * + * An unhandled exception error provides access to an exception and + * stacktrace via the functions Dart_ErrorGetException and + * Dart_ErrorGetStackTrace. + * + * - Compilation error handles are produced when, during the execution + * of Dart code, a compile-time error occurs. As above, this can + * occur in any function which triggers the execution of Dart code. + * + * - Fatal error handles are produced when the system wants to shut + * down the current isolate. + * + * --- Propagating errors --- + * + * When an error handle is returned from the top level invocation of + * Dart code in a program, the embedder must handle the error as they + * see fit. Often, the embedder will print the error message produced + * by Dart_Error and exit the program. + * + * When an error is returned while in the body of a native function, + * it can be propagated up the call stack by calling + * Dart_PropagateError, Dart_SetReturnValue, or Dart_ThrowException. + * Errors should be propagated unless there is a specific reason not + * to. If an error is not propagated then it is ignored. For + * example, if an unhandled exception error is ignored, that + * effectively "catches" the unhandled exception. Fatal errors must + * always be propagated. + * + * When an error is propagated, any current scopes created by + * Dart_EnterScope will be exited. + * + * Using Dart_SetReturnValue to propagate an exception is somewhat + * more convenient than using Dart_PropagateError, and should be + * preferred for reasons discussed below. + * + * Dart_PropagateError and Dart_ThrowException do not return. Instead + * they transfer control non-locally using a setjmp-like mechanism. + * This can be inconvenient if you have resources that you need to + * clean up before propagating the error. + * + * When relying on Dart_PropagateError, we often return error handles + * rather than propagating them from helper functions. Consider the + * following contrived example: + * + * 1 Dart_Handle isLongStringHelper(Dart_Handle arg) { + * 2 intptr_t* length = 0; + * 3 result = Dart_StringLength(arg, &length); + * 4 if (Dart_IsError(result)) { + * 5 return result; + * 6 } + * 7 return Dart_NewBoolean(length > 100); + * 8 } + * 9 + * 10 void NativeFunction_isLongString(Dart_NativeArguments args) { + * 11 Dart_EnterScope(); + * 12 AllocateMyResource(); + * 13 Dart_Handle arg = Dart_GetNativeArgument(args, 0); + * 14 Dart_Handle result = isLongStringHelper(arg); + * 15 if (Dart_IsError(result)) { + * 16 FreeMyResource(); + * 17 Dart_PropagateError(result); + * 18 abort(); // will not reach here + * 19 } + * 20 Dart_SetReturnValue(result); + * 21 FreeMyResource(); + * 22 Dart_ExitScope(); + * 23 } + * + * In this example, we have a native function which calls a helper + * function to do its work. On line 5, the helper function could call + * Dart_PropagateError, but that would not give the native function a + * chance to call FreeMyResource(), causing a leak. Instead, the + * helper function returns the error handle to the caller, giving the + * caller a chance to clean up before propagating the error handle. + * + * When an error is propagated by calling Dart_SetReturnValue, the + * native function will be allowed to complete normally and then the + * exception will be propagated only once the native call + * returns. This can be convenient, as it allows the C code to clean + * up normally. + * + * The example can be written more simply using Dart_SetReturnValue to + * propagate the error. + * + * 1 Dart_Handle isLongStringHelper(Dart_Handle arg) { + * 2 intptr_t* length = 0; + * 3 result = Dart_StringLength(arg, &length); + * 4 if (Dart_IsError(result)) { + * 5 return result + * 6 } + * 7 return Dart_NewBoolean(length > 100); + * 8 } + * 9 + * 10 void NativeFunction_isLongString(Dart_NativeArguments args) { + * 11 Dart_EnterScope(); + * 12 AllocateMyResource(); + * 13 Dart_Handle arg = Dart_GetNativeArgument(args, 0); + * 14 Dart_SetReturnValue(isLongStringHelper(arg)); + * 15 FreeMyResource(); + * 16 Dart_ExitScope(); + * 17 } + * + * In this example, the call to Dart_SetReturnValue on line 14 will + * either return the normal return value or the error (potentially + * generated on line 3). The call to FreeMyResource on line 15 will + * execute in either case. + * + * --- Local and persistent handles --- + * + * Local handles are allocated within the current scope (see + * Dart_EnterScope) and go away when the current scope exits. Unless + * otherwise indicated, callers should assume that all functions in + * the Dart embedding api return local handles. + * + * Persistent handles are allocated within the current isolate. They + * can be used to store objects across scopes. Persistent handles have + * the lifetime of the current isolate unless they are explicitly + * deallocated (see Dart_DeletePersistentHandle). + * The type Dart_Handle represents a handle (both local and persistent). + * The type Dart_PersistentHandle is a Dart_Handle and it is used to + * document that a persistent handle is expected as a parameter to a call + * or the return value from a call is a persistent handle. + * + * FinalizableHandles are persistent handles which are auto deleted when + * the object is garbage collected. It is never safe to use these handles + * unless you know the object is still reachable. + * + * WeakPersistentHandles are persistent handles which are automatically set + * to point Dart_Null when the object is garbage collected. They are not auto + * deleted, so it is safe to use them after the object has become unreachable. + */ +typedef struct _Dart_Handle* Dart_Handle; +typedef Dart_Handle Dart_PersistentHandle; +typedef struct _Dart_WeakPersistentHandle* Dart_WeakPersistentHandle; +typedef struct _Dart_FinalizableHandle* Dart_FinalizableHandle; +// These structs are versioned by DART_API_DL_MAJOR_VERSION, bump the +// version when changing this struct. + +typedef void (*Dart_HandleFinalizer)(void* isolate_callback_data, void* peer); + +/** + * Is this an error handle? + * + * Requires there to be a current isolate. + */ +DART_EXPORT bool Dart_IsError(Dart_Handle handle); + +/** + * Is this an api error handle? + * + * Api error handles are produced when an api function is misused. + * This happens when a Dart embedding api function is called with + * invalid arguments or in an invalid context. + * + * Requires there to be a current isolate. + */ +DART_EXPORT bool Dart_IsApiError(Dart_Handle handle); + +/** + * Is this an unhandled exception error handle? + * + * Unhandled exception error handles are produced when, during the + * execution of Dart code, an exception is thrown but not caught. + * This can occur in any function which triggers the execution of Dart + * code. + * + * See Dart_ErrorGetException and Dart_ErrorGetStackTrace. + * + * Requires there to be a current isolate. + */ +DART_EXPORT bool Dart_IsUnhandledExceptionError(Dart_Handle handle); + +/** + * Is this a compilation error handle? + * + * Compilation error handles are produced when, during the execution + * of Dart code, a compile-time error occurs. This can occur in any + * function which triggers the execution of Dart code. + * + * Requires there to be a current isolate. + */ +DART_EXPORT bool Dart_IsCompilationError(Dart_Handle handle); + +/** + * Is this a fatal error handle? + * + * Fatal error handles are produced when the system wants to shut down + * the current isolate. + * + * Requires there to be a current isolate. + */ +DART_EXPORT bool Dart_IsFatalError(Dart_Handle handle); + +/** + * Gets the error message from an error handle. + * + * Requires there to be a current isolate. + * + * \return A C string containing an error message if the handle is + * error. An empty C string ("") if the handle is valid. This C + * String is scope allocated and is only valid until the next call + * to Dart_ExitScope. +*/ +DART_EXPORT const char* Dart_GetError(Dart_Handle handle); + +/** + * Is this an error handle for an unhandled exception? + */ +DART_EXPORT bool Dart_ErrorHasException(Dart_Handle handle); + +/** + * Gets the exception Object from an unhandled exception error handle. + */ +DART_EXPORT Dart_Handle Dart_ErrorGetException(Dart_Handle handle); + +/** + * Gets the stack trace Object from an unhandled exception error handle. + */ +DART_EXPORT Dart_Handle Dart_ErrorGetStackTrace(Dart_Handle handle); + +/** + * Produces an api error handle with the provided error message. + * + * Requires there to be a current isolate. + * + * \param error the error message. + */ +DART_EXPORT Dart_Handle Dart_NewApiError(const char* error); +DART_EXPORT Dart_Handle Dart_NewCompilationError(const char* error); + +/** + * Produces a new unhandled exception error handle. + * + * Requires there to be a current isolate. + * + * \param exception An instance of a Dart object to be thrown or + * an ApiError or CompilationError handle. + * When an ApiError or CompilationError handle is passed in + * a string object of the error message is created and it becomes + * the Dart object to be thrown. + */ +DART_EXPORT Dart_Handle Dart_NewUnhandledExceptionError(Dart_Handle exception); + +/** + * Propagates an error. + * + * If the provided handle is an unhandled exception error, this + * function will cause the unhandled exception to be rethrown. This + * will proceed in the standard way, walking up Dart frames until an + * appropriate 'catch' block is found, executing 'finally' blocks, + * etc. + * + * If the error is not an unhandled exception error, we will unwind + * the stack to the next C frame. Intervening Dart frames will be + * discarded; specifically, 'finally' blocks will not execute. This + * is the standard way that compilation errors (and the like) are + * handled by the Dart runtime. + * + * In either case, when an error is propagated any current scopes + * created by Dart_EnterScope will be exited. + * + * See the additional discussion under "Propagating Errors" at the + * beginning of this file. + * + * \param An error handle (See Dart_IsError) + * + * \return On success, this function does not return. On failure, the + * process is terminated. + */ +DART_EXPORT void Dart_PropagateError(Dart_Handle handle); + +/** + * Converts an object to a string. + * + * May generate an unhandled exception error. + * + * \return The converted string if no error occurs during + * the conversion. If an error does occur, an error handle is + * returned. + */ +DART_EXPORT Dart_Handle Dart_ToString(Dart_Handle object); + +/** + * Checks to see if two handles refer to identically equal objects. + * + * If both handles refer to instances, this is equivalent to using the top-level + * function identical() from dart:core. Otherwise, returns whether the two + * argument handles refer to the same object. + * + * \param obj1 An object to be compared. + * \param obj2 An object to be compared. + * + * \return True if the objects are identically equal. False otherwise. + */ +DART_EXPORT bool Dart_IdentityEquals(Dart_Handle obj1, Dart_Handle obj2); + +/** + * Allocates a handle in the current scope from a persistent handle. + */ +DART_EXPORT Dart_Handle Dart_HandleFromPersistent(Dart_PersistentHandle object); + +/** + * Allocates a handle in the current scope from a weak persistent handle. + * + * This will be a handle to Dart_Null if the object has been garbage collected. + */ +DART_EXPORT Dart_Handle +Dart_HandleFromWeakPersistent(Dart_WeakPersistentHandle object); + +/** + * Allocates a persistent handle for an object. + * + * This handle has the lifetime of the current isolate unless it is + * explicitly deallocated by calling Dart_DeletePersistentHandle. + * + * Requires there to be a current isolate. + */ +DART_EXPORT Dart_PersistentHandle Dart_NewPersistentHandle(Dart_Handle object); + +/** + * Assign value of local handle to a persistent handle. + * + * Requires there to be a current isolate. + * + * \param obj1 A persistent handle whose value needs to be set. + * \param obj2 An object whose value needs to be set to the persistent handle. + * + * \return Success if the persistent handle was set + * Otherwise, returns an error. + */ +DART_EXPORT void Dart_SetPersistentHandle(Dart_PersistentHandle obj1, + Dart_Handle obj2); + +/** + * Deallocates a persistent handle. + * + * Requires there to be a current isolate group. + */ +DART_EXPORT void Dart_DeletePersistentHandle(Dart_PersistentHandle object); + +/** + * Allocates a weak persistent handle for an object. + * + * This handle has the lifetime of the current isolate. The handle can also be + * explicitly deallocated by calling Dart_DeleteWeakPersistentHandle. + * + * If the object becomes unreachable the callback is invoked with the peer as + * argument. The callback can be executed on any thread, will have a current + * isolate group, but will not have a current isolate. The callback can only + * call Dart_DeletePersistentHandle or Dart_DeleteWeakPersistentHandle. This + * gives the embedder the ability to cleanup data associated with the object. + * The handle will point to the Dart_Null object after the finalizer has been + * run. It is illegal to call into the VM with any other Dart_* functions from + * the callback. If the handle is deleted before the object becomes + * unreachable, the callback is never invoked. + * + * Requires there to be a current isolate. + * + * \param object An object. + * \param peer A pointer to a native object or NULL. This value is + * provided to callback when it is invoked. + * \param external_allocation_size The number of externally allocated + * bytes for peer. Used to inform the garbage collector. + * \param callback A function pointer that will be invoked sometime + * after the object is garbage collected, unless the handle has been deleted. + * A valid callback needs to be specified it cannot be NULL. + * + * \return The weak persistent handle or NULL. NULL is returned in case of bad + * parameters. + */ +DART_EXPORT Dart_WeakPersistentHandle +Dart_NewWeakPersistentHandle(Dart_Handle object, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); + +/** + * Deletes the given weak persistent [object] handle. + * + * Requires there to be a current isolate group. + */ +DART_EXPORT void Dart_DeleteWeakPersistentHandle( + Dart_WeakPersistentHandle object); + +/** + * Updates the external memory size for the given weak persistent handle. + * + * May trigger garbage collection. + */ +DART_EXPORT void Dart_UpdateExternalSize(Dart_WeakPersistentHandle object, + intptr_t external_allocation_size); + +/** + * Allocates a finalizable handle for an object. + * + * This handle has the lifetime of the current isolate group unless the object + * pointed to by the handle is garbage collected, in this case the VM + * automatically deletes the handle after invoking the callback associated + * with the handle. The handle can also be explicitly deallocated by + * calling Dart_DeleteFinalizableHandle. + * + * If the object becomes unreachable the callback is invoked with the + * the peer as argument. The callback can be executed on any thread, will have + * an isolate group, but will not have a current isolate. The callback can only + * call Dart_DeletePersistentHandle or Dart_DeleteWeakPersistentHandle. + * This gives the embedder the ability to cleanup data associated with the + * object and clear out any cached references to the handle. All references to + * this handle after the callback will be invalid. It is illegal to call into + * the VM with any other Dart_* functions from the callback. If the handle is + * deleted before the object becomes unreachable, the callback is never + * invoked. + * + * Requires there to be a current isolate. + * + * \param object An object. + * \param peer A pointer to a native object or NULL. This value is + * provided to callback when it is invoked. + * \param external_allocation_size The number of externally allocated + * bytes for peer. Used to inform the garbage collector. + * \param callback A function pointer that will be invoked sometime + * after the object is garbage collected, unless the handle has been deleted. + * A valid callback needs to be specified it cannot be NULL. + * + * \return The finalizable handle or NULL. NULL is returned in case of bad + * parameters. + */ +DART_EXPORT Dart_FinalizableHandle +Dart_NewFinalizableHandle(Dart_Handle object, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); + +/** + * Deletes the given finalizable [object] handle. + * + * The caller has to provide the actual Dart object the handle was created from + * to prove the object (and therefore the finalizable handle) is still alive. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_DeleteFinalizableHandle(Dart_FinalizableHandle object, + Dart_Handle strong_ref_to_object); + +/** + * Updates the external memory size for the given finalizable handle. + * + * The caller has to provide the actual Dart object the handle was created from + * to prove the object (and therefore the finalizable handle) is still alive. + * + * May trigger garbage collection. + */ +DART_EXPORT void Dart_UpdateFinalizableExternalSize( + Dart_FinalizableHandle object, + Dart_Handle strong_ref_to_object, + intptr_t external_allocation_size); + +/* + * ========================== + * Initialization and Globals + * ========================== + */ + +/** + * Gets the version string for the Dart VM. + * + * The version of the Dart VM can be accessed without initializing the VM. + * + * \return The version string for the embedded Dart VM. + */ +DART_EXPORT const char* Dart_VersionString(); + +typedef struct { + const char* library_uri; + const char* class_name; + const char* function_name; +} Dart_QualifiedFunctionName; + +/** + * Isolate specific flags are set when creating a new isolate using the + * Dart_IsolateFlags structure. + * + * Current version of flags is encoded in a 32-bit integer with 16 bits used + * for each part. + */ + +#define DART_FLAGS_CURRENT_VERSION (0x0000000c) + +typedef struct { + int32_t version; + bool enable_asserts; + bool use_field_guards; + bool use_osr; + bool obfuscate; + Dart_QualifiedFunctionName* entry_points; + bool load_vmservice_library; + bool copy_parent_code; + bool null_safety; + bool is_system_isolate; +} Dart_IsolateFlags; + +/** + * Initialize Dart_IsolateFlags with correct version and default values. + */ +DART_EXPORT void Dart_IsolateFlagsInitialize(Dart_IsolateFlags* flags); + +/** + * An isolate creation and initialization callback function. + * + * This callback, provided by the embedder, is called when the VM + * needs to create an isolate. The callback should create an isolate + * by calling Dart_CreateIsolateGroup and load any scripts required for + * execution. + * + * This callback may be called on a different thread than the one + * running the parent isolate. + * + * When the function returns NULL, it is the responsibility of this + * function to ensure that Dart_ShutdownIsolate has been called if + * required (for example, if the isolate was created successfully by + * Dart_CreateIsolateGroup() but the root library fails to load + * successfully, then the function should call Dart_ShutdownIsolate + * before returning). + * + * When the function returns NULL, the function should set *error to + * a malloc-allocated buffer containing a useful error message. The + * caller of this function (the VM) will make sure that the buffer is + * freed. + * + * \param script_uri The uri of the main source file or snapshot to load. + * Either the URI of the parent isolate set in Dart_CreateIsolateGroup for + * Isolate.spawn, or the argument to Isolate.spawnUri canonicalized by the + * library tag handler of the parent isolate. + * The callback is responsible for loading the program by a call to + * Dart_LoadScriptFromKernel. + * \param main The name of the main entry point this isolate will + * eventually run. This is provided for advisory purposes only to + * improve debugging messages. The main function is not invoked by + * this function. + * \param package_root Ignored. + * \param package_config Uri of the package configuration file (either in format + * of .packages or .dart_tool/package_config.json) for this isolate + * to resolve package imports against. If this parameter is not passed the + * package resolution of the parent isolate should be used. + * \param flags Default flags for this isolate being spawned. Either inherited + * from the spawning isolate or passed as parameters when spawning the + * isolate from Dart code. + * \param isolate_data The isolate data which was passed to the + * parent isolate when it was created by calling Dart_CreateIsolateGroup(). + * \param error A structure into which the embedder can place a + * C string containing an error message in the case of failures. + * + * \return The embedder returns NULL if the creation and + * initialization was not successful and the isolate if successful. + */ +typedef Dart_Isolate (*Dart_IsolateGroupCreateCallback)( + const char* script_uri, + const char* main, + const char* package_root, + const char* package_config, + Dart_IsolateFlags* flags, + void* isolate_data, + char** error); + +/** + * An isolate initialization callback function. + * + * This callback, provided by the embedder, is called when the VM has created an + * isolate within an existing isolate group (i.e. from the same source as an + * existing isolate). + * + * The callback should setup native resolvers and might want to set a custom + * message handler via [Dart_SetMessageNotifyCallback] and mark the isolate as + * runnable. + * + * This callback may be called on a different thread than the one + * running the parent isolate. + * + * When the function returns `false`, it is the responsibility of this + * function to ensure that `Dart_ShutdownIsolate` has been called. + * + * When the function returns `false`, the function should set *error to + * a malloc-allocated buffer containing a useful error message. The + * caller of this function (the VM) will make sure that the buffer is + * freed. + * + * \param child_isolate_data The callback data to associate with the new + * child isolate. + * \param error A structure into which the embedder can place a + * C string containing an error message in the case the initialization fails. + * + * \return The embedder returns true if the initialization was successful and + * false otherwise (in which case the VM will terminate the isolate). + */ +typedef bool (*Dart_InitializeIsolateCallback)(void** child_isolate_data, + char** error); + +/** + * An isolate unhandled exception callback function. + * + * This callback has been DEPRECATED. + */ +typedef void (*Dart_IsolateUnhandledExceptionCallback)(Dart_Handle error); + +/** + * An isolate shutdown callback function. + * + * This callback, provided by the embedder, is called before the vm + * shuts down an isolate. The isolate being shutdown will be the current + * isolate. It is safe to run Dart code. + * + * This function should be used to dispose of native resources that + * are allocated to an isolate in order to avoid leaks. + * + * \param isolate_group_data The same callback data which was passed to the + * isolate group when it was created. + * \param isolate_data The same callback data which was passed to the isolate + * when it was created. + */ +typedef void (*Dart_IsolateShutdownCallback)(void* isolate_group_data, + void* isolate_data); + +/** + * An isolate cleanup callback function. + * + * This callback, provided by the embedder, is called after the vm + * shuts down an isolate. There will be no current isolate and it is *not* + * safe to run Dart code. + * + * This function should be used to dispose of native resources that + * are allocated to an isolate in order to avoid leaks. + * + * \param isolate_group_data The same callback data which was passed to the + * isolate group when it was created. + * \param isolate_data The same callback data which was passed to the isolate + * when it was created. + */ +typedef void (*Dart_IsolateCleanupCallback)(void* isolate_group_data, + void* isolate_data); + +/** + * An isolate group cleanup callback function. + * + * This callback, provided by the embedder, is called after the vm + * shuts down an isolate group. + * + * This function should be used to dispose of native resources that + * are allocated to an isolate in order to avoid leaks. + * + * \param isolate_group_data The same callback data which was passed to the + * isolate group when it was created. + * + */ +typedef void (*Dart_IsolateGroupCleanupCallback)(void* isolate_group_data); + +/** + * A thread death callback function. + * This callback, provided by the embedder, is called before a thread in the + * vm thread pool exits. + * This function could be used to dispose of native resources that + * are associated and attached to the thread, in order to avoid leaks. + */ +typedef void (*Dart_ThreadExitCallback)(); + +/** + * Callbacks provided by the embedder for file operations. If the + * embedder does not allow file operations these callbacks can be + * NULL. + * + * Dart_FileOpenCallback - opens a file for reading or writing. + * \param name The name of the file to open. + * \param write A boolean variable which indicates if the file is to + * opened for writing. If there is an existing file it needs to truncated. + * + * Dart_FileReadCallback - Read contents of file. + * \param data Buffer allocated in the callback into which the contents + * of the file are read into. It is the responsibility of the caller to + * free this buffer. + * \param file_length A variable into which the length of the file is returned. + * In the case of an error this value would be -1. + * \param stream Handle to the opened file. + * + * Dart_FileWriteCallback - Write data into file. + * \param data Buffer which needs to be written into the file. + * \param length Length of the buffer. + * \param stream Handle to the opened file. + * + * Dart_FileCloseCallback - Closes the opened file. + * \param stream Handle to the opened file. + * + */ +typedef void* (*Dart_FileOpenCallback)(const char* name, bool write); + +typedef void (*Dart_FileReadCallback)(uint8_t** data, + intptr_t* file_length, + void* stream); + +typedef void (*Dart_FileWriteCallback)(const void* data, + intptr_t length, + void* stream); + +typedef void (*Dart_FileCloseCallback)(void* stream); + +typedef bool (*Dart_EntropySource)(uint8_t* buffer, intptr_t length); + +/** + * Callback provided by the embedder that is used by the vmservice isolate + * to request the asset archive. The asset archive must be an uncompressed tar + * archive that is stored in a Uint8List. + * + * If the embedder has no vmservice isolate assets, the callback can be NULL. + * + * \return The embedder must return a handle to a Uint8List containing an + * uncompressed tar archive or null. + */ +typedef Dart_Handle (*Dart_GetVMServiceAssetsArchive)(); + +/** + * The current version of the Dart_InitializeFlags. Should be incremented every + * time Dart_InitializeFlags changes in a binary incompatible way. + */ +#define DART_INITIALIZE_PARAMS_CURRENT_VERSION (0x00000004) + +/** Forward declaration */ +struct Dart_CodeObserver; + +/** + * Callback provided by the embedder that is used by the VM to notify on code + * object creation, *before* it is invoked the first time. + * This is useful for embedders wanting to e.g. keep track of PCs beyond + * the lifetime of the garbage collected code objects. + * Note that an address range may be used by more than one code object over the + * lifecycle of a process. Clients of this function should record timestamps for + * these compilation events and when collecting PCs to disambiguate reused + * address ranges. + */ +typedef void (*Dart_OnNewCodeCallback)(struct Dart_CodeObserver* observer, + const char* name, + uintptr_t base, + uintptr_t size); + +typedef struct Dart_CodeObserver { + void* data; + + Dart_OnNewCodeCallback on_new_code; +} Dart_CodeObserver; + +/** + * Describes how to initialize the VM. Used with Dart_Initialize. + * + * \param version Identifies the version of the struct used by the client. + * should be initialized to DART_INITIALIZE_PARAMS_CURRENT_VERSION. + * \param vm_isolate_snapshot A buffer containing a snapshot of the VM isolate + * or NULL if no snapshot is provided. If provided, the buffer must remain + * valid until Dart_Cleanup returns. + * \param instructions_snapshot A buffer containing a snapshot of precompiled + * instructions, or NULL if no snapshot is provided. If provided, the buffer + * must remain valid until Dart_Cleanup returns. + * \param initialize_isolate A function to be called during isolate + * initialization inside an existing isolate group. + * See Dart_InitializeIsolateCallback. + * \param create_group A function to be called during isolate group creation. + * See Dart_IsolateGroupCreateCallback. + * \param shutdown A function to be called right before an isolate is shutdown. + * See Dart_IsolateShutdownCallback. + * \param cleanup A function to be called after an isolate was shutdown. + * See Dart_IsolateCleanupCallback. + * \param cleanup_group A function to be called after an isolate group is shutdown. + * See Dart_IsolateGroupCleanupCallback. + * \param get_service_assets A function to be called by the service isolate when + * it requires the vmservice assets archive. + * See Dart_GetVMServiceAssetsArchive. + * \param code_observer An external code observer callback function. + * The observer can be invoked as early as during the Dart_Initialize() call. + */ +typedef struct { + int32_t version; + const uint8_t* vm_snapshot_data; + const uint8_t* vm_snapshot_instructions; + Dart_IsolateGroupCreateCallback create_group; + Dart_InitializeIsolateCallback initialize_isolate; + Dart_IsolateShutdownCallback shutdown_isolate; + Dart_IsolateCleanupCallback cleanup_isolate; + Dart_IsolateGroupCleanupCallback cleanup_group; + Dart_ThreadExitCallback thread_exit; + Dart_FileOpenCallback file_open; + Dart_FileReadCallback file_read; + Dart_FileWriteCallback file_write; + Dart_FileCloseCallback file_close; + Dart_EntropySource entropy_source; + Dart_GetVMServiceAssetsArchive get_service_assets; + bool start_kernel_isolate; + Dart_CodeObserver* code_observer; +} Dart_InitializeParams; + +/** + * Initializes the VM. + * + * \param params A struct containing initialization information. The version + * field of the struct must be DART_INITIALIZE_PARAMS_CURRENT_VERSION. + * + * \return NULL if initialization is successful. Returns an error message + * otherwise. The caller is responsible for freeing the error message. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_Initialize( + Dart_InitializeParams* params); + +/** + * Cleanup state in the VM before process termination. + * + * \return NULL if cleanup is successful. Returns an error message otherwise. + * The caller is responsible for freeing the error message. + * + * NOTE: This function must not be called on a thread that was created by the VM + * itself. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_Cleanup(); + +/** + * Sets command line flags. Should be called before Dart_Initialize. + * + * \param argc The length of the arguments array. + * \param argv An array of arguments. + * + * \return NULL if successful. Returns an error message otherwise. + * The caller is responsible for freeing the error message. + * + * NOTE: This call does not store references to the passed in c-strings. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_SetVMFlags(int argc, + const char** argv); + +/** + * Returns true if the named VM flag is of boolean type, specified, and set to + * true. + * + * \param flag_name The name of the flag without leading punctuation + * (example: "enable_asserts"). + */ +DART_EXPORT bool Dart_IsVMFlagSet(const char* flag_name); + +/* + * ======== + * Isolates + * ======== + */ + +/** + * Creates a new isolate. The new isolate becomes the current isolate. + * + * A snapshot can be used to restore the VM quickly to a saved state + * and is useful for fast startup. If snapshot data is provided, the + * isolate will be started using that snapshot data. Requires a core snapshot or + * an app snapshot created by Dart_CreateSnapshot or + * Dart_CreatePrecompiledSnapshot* from a VM with the same version. + * + * Requires there to be no current isolate. + * + * \param script_uri The main source file or snapshot this isolate will load. + * The VM will provide this URI to the Dart_IsolateGroupCreateCallback when a child + * isolate is created by Isolate.spawn. The embedder should use a URI that + * allows it to load the same program into such a child isolate. + * \param name A short name for the isolate to improve debugging messages. + * Typically of the format 'foo.dart:main()'. + * \param isolate_snapshot_data + * \param isolate_snapshot_instructions Buffers containing a snapshot of the + * isolate or NULL if no snapshot is provided. If provided, the buffers must + * remain valid until the isolate shuts down. + * \param flags Pointer to VM specific flags or NULL for default flags. + * \param isolate_group_data Embedder group data. This data can be obtained + * by calling Dart_IsolateGroupData and will be passed to the + * Dart_IsolateShutdownCallback, Dart_IsolateCleanupCallback, and + * Dart_IsolateGroupCleanupCallback. + * \param isolate_data Embedder data. This data will be passed to + * the Dart_IsolateGroupCreateCallback when new isolates are spawned from + * this parent isolate. + * \param error Returns NULL if creation is successful, an error message + * otherwise. The caller is responsible for calling free() on the error + * message. + * + * \return The new isolate on success, or NULL if isolate creation failed. + */ +DART_EXPORT Dart_Isolate +Dart_CreateIsolateGroup(const char* script_uri, + const char* name, + const uint8_t* isolate_snapshot_data, + const uint8_t* isolate_snapshot_instructions, + Dart_IsolateFlags* flags, + void* isolate_group_data, + void* isolate_data, + char** error); +/** + * Creates a new isolate inside the isolate group of [group_member]. + * + * Requires there to be no current isolate. + * + * \param group_member An isolate from the same group into which the newly created + * isolate should be born into. Other threads may not have entered / enter this + * member isolate. + * \param name A short name for the isolate for debugging purposes. + * \param shutdown_callback A callback to be called when the isolate is being + * shutdown (may be NULL). + * \param cleanup_callback A callback to be called when the isolate is being + * cleaned up (may be NULL). + * \param isolate_data The embedder-specific data associated with this isolate. + * \param error Set to NULL if creation is successful, set to an error + * message otherwise. The caller is responsible for calling free() on the + * error message. + * + * \return The newly created isolate on success, or NULL if isolate creation + * failed. + * + * If successful, the newly created isolate will become the current isolate. + */ +DART_EXPORT Dart_Isolate +Dart_CreateIsolateInGroup(Dart_Isolate group_member, + const char* name, + Dart_IsolateShutdownCallback shutdown_callback, + Dart_IsolateCleanupCallback cleanup_callback, + void* child_isolate_data, + char** error); + +/* TODO(turnidge): Document behavior when there is already a current + * isolate. */ + +/** + * Creates a new isolate from a Dart Kernel file. The new isolate + * becomes the current isolate. + * + * Requires there to be no current isolate. + * + * \param script_uri The main source file or snapshot this isolate will load. + * The VM will provide this URI to the Dart_IsolateGroupCreateCallback when a child + * isolate is created by Isolate.spawn. The embedder should use a URI that + * allows it to load the same program into such a child isolate. + * \param name A short name for the isolate to improve debugging messages. + * Typically of the format 'foo.dart:main()'. + * \param kernel_buffer + * \param kernel_buffer_size A buffer which contains a kernel/DIL program. Must + * remain valid until isolate shutdown. + * \param flags Pointer to VM specific flags or NULL for default flags. + * \param isolate_group_data Embedder group data. This data can be obtained + * by calling Dart_IsolateGroupData and will be passed to the + * Dart_IsolateShutdownCallback, Dart_IsolateCleanupCallback, and + * Dart_IsolateGroupCleanupCallback. + * \param isolate_data Embedder data. This data will be passed to + * the Dart_IsolateGroupCreateCallback when new isolates are spawned from + * this parent isolate. + * \param error Returns NULL if creation is successful, an error message + * otherwise. The caller is responsible for calling free() on the error + * message. + * + * \return The new isolate on success, or NULL if isolate creation failed. + */ +DART_EXPORT Dart_Isolate +Dart_CreateIsolateGroupFromKernel(const char* script_uri, + const char* name, + const uint8_t* kernel_buffer, + intptr_t kernel_buffer_size, + Dart_IsolateFlags* flags, + void* isolate_group_data, + void* isolate_data, + char** error); +/** + * Shuts down the current isolate. After this call, the current isolate is NULL. + * Any current scopes created by Dart_EnterScope will be exited. Invokes the + * shutdown callback and any callbacks of remaining weak persistent handles. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_ShutdownIsolate(); +/* TODO(turnidge): Document behavior when there is no current isolate. */ + +/** + * Returns the current isolate. Will return NULL if there is no + * current isolate. + */ +DART_EXPORT Dart_Isolate Dart_CurrentIsolate(); + +/** + * Returns the callback data associated with the current isolate. This + * data was set when the isolate got created or initialized. + */ +DART_EXPORT void* Dart_CurrentIsolateData(); + +/** + * Returns the callback data associated with the given isolate. This + * data was set when the isolate got created or initialized. + */ +DART_EXPORT void* Dart_IsolateData(Dart_Isolate isolate); + +/** + * Returns the current isolate group. Will return NULL if there is no + * current isolate group. + */ +DART_EXPORT Dart_IsolateGroup Dart_CurrentIsolateGroup(); + +/** + * Returns the callback data associated with the current isolate group. This + * data was passed to the isolate group when it was created. + */ +DART_EXPORT void* Dart_CurrentIsolateGroupData(); + +/** + * Returns the callback data associated with the specified isolate group. This + * data was passed to the isolate when it was created. + * The embedder is responsible for ensuring the consistency of this data + * with respect to the lifecycle of an isolate group. + */ +DART_EXPORT void* Dart_IsolateGroupData(Dart_Isolate isolate); + +/** + * Returns the debugging name for the current isolate. + * + * This name is unique to each isolate and should only be used to make + * debugging messages more comprehensible. + */ +DART_EXPORT Dart_Handle Dart_DebugName(); + +/** + * Returns the ID for an isolate which is used to query the service protocol. + * + * It is the responsibility of the caller to free the returned ID. + */ +DART_EXPORT const char* Dart_IsolateServiceId(Dart_Isolate isolate); + +/** + * Enters an isolate. After calling this function, + * the current isolate will be set to the provided isolate. + * + * Requires there to be no current isolate. Multiple threads may not be in + * the same isolate at once. + */ +DART_EXPORT void Dart_EnterIsolate(Dart_Isolate isolate); + +/** + * Kills the given isolate. + * + * This function has the same effect as dart:isolate's + * Isolate.kill(priority:immediate). + * It can interrupt ordinary Dart code but not native code. If the isolate is + * in the middle of a long running native function, the isolate will not be + * killed until control returns to Dart. + * + * Does not require a current isolate. It is safe to kill the current isolate if + * there is one. + */ +DART_EXPORT void Dart_KillIsolate(Dart_Isolate isolate); + +/** + * Notifies the VM that the embedder expects |size| bytes of memory have become + * unreachable. The VM may use this hint to adjust the garbage collector's + * growth policy. + * + * Multiple calls are interpreted as increasing, not replacing, the estimate of + * unreachable memory. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_HintFreed(intptr_t size); + +/** + * Notifies the VM that the embedder expects to be idle until |deadline|. The VM + * may use this time to perform garbage collection or other tasks to avoid + * delays during execution of Dart code in the future. + * + * |deadline| is measured in microseconds against the system's monotonic time. + * This clock can be accessed via Dart_TimelineGetMicros(). + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_NotifyIdle(int64_t deadline); + +/** + * Notifies the VM that the system is running low on memory. + * + * Does not require a current isolate. Only valid after calling Dart_Initialize. + */ +DART_EXPORT void Dart_NotifyLowMemory(); + +/** + * Starts the CPU sampling profiler. + */ +DART_EXPORT void Dart_StartProfiling(); + +/** + * Stops the CPU sampling profiler. + * + * Note that some profile samples might still be taken after this fucntion + * returns due to the asynchronous nature of the implementation on some + * platforms. + */ +DART_EXPORT void Dart_StopProfiling(); + +/** + * Notifies the VM that the current thread should not be profiled until a + * matching call to Dart_ThreadEnableProfiling is made. + * + * NOTE: By default, if a thread has entered an isolate it will be profiled. + * This function should be used when an embedder knows a thread is about + * to make a blocking call and wants to avoid unnecessary interrupts by + * the profiler. + */ +DART_EXPORT void Dart_ThreadDisableProfiling(); + +/** + * Notifies the VM that the current thread should be profiled. + * + * NOTE: It is only legal to call this function *after* calling + * Dart_ThreadDisableProfiling. + * + * NOTE: By default, if a thread has entered an isolate it will be profiled. + */ +DART_EXPORT void Dart_ThreadEnableProfiling(); + +/** + * Register symbol information for the Dart VM's profiler and crash dumps. + * + * This consumes the output of //topaz/runtime/dart/profiler_symbols, which + * should be treated as opaque. + */ +DART_EXPORT void Dart_AddSymbols(const char* dso_name, + void* buffer, + intptr_t buffer_size); + +/** + * Exits an isolate. After this call, Dart_CurrentIsolate will + * return NULL. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_ExitIsolate(); +/* TODO(turnidge): We don't want users of the api to be able to exit a + * "pure" dart isolate. Implement and document. */ + +/** + * Creates a full snapshot of the current isolate heap. + * + * A full snapshot is a compact representation of the dart vm isolate heap + * and dart isolate heap states. These snapshots are used to initialize + * the vm isolate on startup and fast initialization of an isolate. + * A Snapshot of the heap is created before any dart code has executed. + * + * Requires there to be a current isolate. Not available in the precompiled + * runtime (check Dart_IsPrecompiledRuntime). + * + * \param buffer Returns a pointer to a buffer containing the + * snapshot. This buffer is scope allocated and is only valid + * until the next call to Dart_ExitScope. + * \param size Returns the size of the buffer. + * \param is_core Create a snapshot containing core libraries. + * Such snapshot should be agnostic to null safety mode. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateSnapshot(uint8_t** vm_snapshot_data_buffer, + intptr_t* vm_snapshot_data_size, + uint8_t** isolate_snapshot_data_buffer, + intptr_t* isolate_snapshot_data_size, + bool is_core); + +/** + * Returns whether the buffer contains a kernel file. + * + * \param buffer Pointer to a buffer that might contain a kernel binary. + * \param buffer_size Size of the buffer. + * + * \return Whether the buffer contains a kernel binary (full or partial). + */ +DART_EXPORT bool Dart_IsKernel(const uint8_t* buffer, intptr_t buffer_size); + +/** + * Make isolate runnable. + * + * When isolates are spawned, this function is used to indicate that + * the creation and initialization (including script loading) of the + * isolate is complete and the isolate can start. + * This function expects there to be no current isolate. + * + * \param isolate The isolate to be made runnable. + * + * \return NULL if successful. Returns an error message otherwise. The caller + * is responsible for freeing the error message. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_IsolateMakeRunnable( + Dart_Isolate isolate); + +/* + * ================== + * Messages and Ports + * ================== + */ + +/** + * A port is used to send or receive inter-isolate messages + */ +typedef int64_t Dart_Port; + +/** + * ILLEGAL_PORT is a port number guaranteed never to be associated with a valid + * port. + */ +#define ILLEGAL_PORT ((Dart_Port)0) + +/** + * A message notification callback. + * + * This callback allows the embedder to provide an alternate wakeup + * mechanism for the delivery of inter-isolate messages. It is the + * responsibility of the embedder to call Dart_HandleMessage to + * process the message. + */ +typedef void (*Dart_MessageNotifyCallback)(Dart_Isolate dest_isolate); + +/** + * Allows embedders to provide an alternative wakeup mechanism for the + * delivery of inter-isolate messages. This setting only applies to + * the current isolate. + * + * Most embedders will only call this function once, before isolate + * execution begins. If this function is called after isolate + * execution begins, the embedder is responsible for threading issues. + */ +DART_EXPORT void Dart_SetMessageNotifyCallback( + Dart_MessageNotifyCallback message_notify_callback); +/* TODO(turnidge): Consider moving this to isolate creation so that it + * is impossible to mess up. */ + +/** + * Query the current message notify callback for the isolate. + * + * \return The current message notify callback for the isolate. + */ +DART_EXPORT Dart_MessageNotifyCallback Dart_GetMessageNotifyCallback(); + +/** + * The VM's default message handler supports pausing an isolate before it + * processes the first message and right after the it processes the isolate's + * final message. This can be controlled for all isolates by two VM flags: + * + * `--pause-isolates-on-start` + * `--pause-isolates-on-exit` + * + * Additionally, Dart_SetShouldPauseOnStart and Dart_SetShouldPauseOnExit can be + * used to control this behaviour on a per-isolate basis. + * + * When an embedder is using a Dart_MessageNotifyCallback the embedder + * needs to cooperate with the VM so that the service protocol can report + * accurate information about isolates and so that tools such as debuggers + * work reliably. + * + * The following functions can be used to implement pausing on start and exit. + */ + +/** + * If the VM flag `--pause-isolates-on-start` was passed this will be true. + * + * \return A boolean value indicating if pause on start was requested. + */ +DART_EXPORT bool Dart_ShouldPauseOnStart(); + +/** + * Override the VM flag `--pause-isolates-on-start` for the current isolate. + * + * \param should_pause Should the isolate be paused on start? + * + * NOTE: This must be called before Dart_IsolateMakeRunnable. + */ +DART_EXPORT void Dart_SetShouldPauseOnStart(bool should_pause); + +/** + * Is the current isolate paused on start? + * + * \return A boolean value indicating if the isolate is paused on start. + */ +DART_EXPORT bool Dart_IsPausedOnStart(); + +/** + * Called when the embedder has paused the current isolate on start and when + * the embedder has resumed the isolate. + * + * \param paused Is the isolate paused on start? + */ +DART_EXPORT void Dart_SetPausedOnStart(bool paused); + +/** + * If the VM flag `--pause-isolates-on-exit` was passed this will be true. + * + * \return A boolean value indicating if pause on exit was requested. + */ +DART_EXPORT bool Dart_ShouldPauseOnExit(); + +/** + * Override the VM flag `--pause-isolates-on-exit` for the current isolate. + * + * \param should_pause Should the isolate be paused on exit? + * + */ +DART_EXPORT void Dart_SetShouldPauseOnExit(bool should_pause); + +/** + * Is the current isolate paused on exit? + * + * \return A boolean value indicating if the isolate is paused on exit. + */ +DART_EXPORT bool Dart_IsPausedOnExit(); + +/** + * Called when the embedder has paused the current isolate on exit and when + * the embedder has resumed the isolate. + * + * \param paused Is the isolate paused on exit? + */ +DART_EXPORT void Dart_SetPausedOnExit(bool paused); + +/** + * Called when the embedder has caught a top level unhandled exception error + * in the current isolate. + * + * NOTE: It is illegal to call this twice on the same isolate without first + * clearing the sticky error to null. + * + * \param error The unhandled exception error. + */ +DART_EXPORT void Dart_SetStickyError(Dart_Handle error); + +/** + * Does the current isolate have a sticky error? + */ +DART_EXPORT bool Dart_HasStickyError(); + +/** + * Gets the sticky error for the current isolate. + * + * \return A handle to the sticky error object or null. + */ +DART_EXPORT Dart_Handle Dart_GetStickyError(); + +/** + * Handles the next pending message for the current isolate. + * + * May generate an unhandled exception error. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_HandleMessage(); + +/** + * Drains the microtask queue, then blocks the calling thread until the current + * isolate recieves a message, then handles all messages. + * + * \param timeout_millis When non-zero, the call returns after the indicated + number of milliseconds even if no message was received. + * \return A valid handle if no error occurs, otherwise an error handle. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_WaitForEvent(int64_t timeout_millis); + +/** + * Handles any pending messages for the vm service for the current + * isolate. + * + * This function may be used by an embedder at a breakpoint to avoid + * pausing the vm service. + * + * This function can indirectly cause the message notify callback to + * be called. + * + * \return true if the vm service requests the program resume + * execution, false otherwise + */ +DART_EXPORT bool Dart_HandleServiceMessages(); + +/** + * Does the current isolate have pending service messages? + * + * \return true if the isolate has pending service messages, false otherwise. + */ +DART_EXPORT bool Dart_HasServiceMessages(); + +/** + * Processes any incoming messages for the current isolate. + * + * This function may only be used when the embedder has not provided + * an alternate message delivery mechanism with + * Dart_SetMessageCallbacks. It is provided for convenience. + * + * This function waits for incoming messages for the current + * isolate. As new messages arrive, they are handled using + * Dart_HandleMessage. The routine exits when all ports to the + * current isolate are closed. + * + * \return A valid handle if the run loop exited successfully. If an + * exception or other error occurs while processing messages, an + * error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_RunLoop(); + +/** + * Lets the VM run message processing for the isolate. + * + * This function expects there to a current isolate and the current isolate + * must not have an active api scope. The VM will take care of making the + * isolate runnable (if not already), handles its message loop and will take + * care of shutting the isolate down once it's done. + * + * \param errors_are_fatal Whether uncaught errors should be fatal. + * \param on_error_port A port to notify on uncaught errors (or ILLEGAL_PORT). + * \param on_exit_port A port to notify on exit (or ILLEGAL_PORT). + * \param error A non-NULL pointer which will hold an error message if the call + * fails. The error has to be free()ed by the caller. + * + * \return If successfull the VM takes owernship of the isolate and takes care + * of its message loop. If not successful the caller retains owernship of the + * isolate. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT bool Dart_RunLoopAsync( + bool errors_are_fatal, + Dart_Port on_error_port, + Dart_Port on_exit_port, + char** error); + +/* TODO(turnidge): Should this be removed from the public api? */ + +/** + * Gets the main port id for the current isolate. + */ +DART_EXPORT Dart_Port Dart_GetMainPortId(); + +/** + * Does the current isolate have live ReceivePorts? + * + * A ReceivePort is live when it has not been closed. + */ +DART_EXPORT bool Dart_HasLivePorts(); + +/** + * Posts a message for some isolate. The message is a serialized + * object. + * + * Requires there to be a current isolate. + * + * \param port The destination port. + * \param object An object from the current isolate. + * + * \return True if the message was posted. + */ +DART_EXPORT bool Dart_Post(Dart_Port port_id, Dart_Handle object); + +/** + * Returns a new SendPort with the provided port id. + * + * \param port_id The destination port. + * + * \return A new SendPort if no errors occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewSendPort(Dart_Port port_id); + +/** + * Gets the SendPort id for the provided SendPort. + * \param port A SendPort object whose id is desired. + * \param port_id Returns the id of the SendPort. + * \return Success if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_SendPortGetId(Dart_Handle port, + Dart_Port* port_id); + +/* + * ====== + * Scopes + * ====== + */ + +/** + * Enters a new scope. + * + * All new local handles will be created in this scope. Additionally, + * some functions may return "scope allocated" memory which is only + * valid within this scope. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_EnterScope(); + +/** + * Exits a scope. + * + * The previous scope (if any) becomes the current scope. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_ExitScope(); + +/** + * The Dart VM uses "zone allocation" for temporary structures. Zones + * support very fast allocation of small chunks of memory. The chunks + * cannot be deallocated individually, but instead zones support + * deallocating all chunks in one fast operation. + * + * This function makes it possible for the embedder to allocate + * temporary data in the VMs zone allocator. + * + * Zone allocation is possible: + * 1. when inside a scope where local handles can be allocated + * 2. when processing a message from a native port in a native port + * handler + * + * All the memory allocated this way will be reclaimed either on the + * next call to Dart_ExitScope or when the native port handler exits. + * + * \param size Size of the memory to allocate. + * + * \return A pointer to the allocated memory. NULL if allocation + * failed. Failure might due to is no current VM zone. + */ +DART_EXPORT uint8_t* Dart_ScopeAllocate(intptr_t size); + +/* + * ======= + * Objects + * ======= + */ + +/** + * Returns the null object. + * + * \return A handle to the null object. + */ +DART_EXPORT Dart_Handle Dart_Null(); + +/** + * Is this object null? + */ +DART_EXPORT bool Dart_IsNull(Dart_Handle object); + +/** + * Returns the empty string object. + * + * \return A handle to the empty string object. + */ +DART_EXPORT Dart_Handle Dart_EmptyString(); + +/** + * Returns types that are not classes, and which therefore cannot be looked up + * as library members by Dart_GetType. + * + * \return A handle to the dynamic, void or Never type. + */ +DART_EXPORT Dart_Handle Dart_TypeDynamic(); +DART_EXPORT Dart_Handle Dart_TypeVoid(); +DART_EXPORT Dart_Handle Dart_TypeNever(); + +/** + * Checks if the two objects are equal. + * + * The result of the comparison is returned through the 'equal' + * parameter. The return value itself is used to indicate success or + * failure, not equality. + * + * May generate an unhandled exception error. + * + * \param obj1 An object to be compared. + * \param obj2 An object to be compared. + * \param equal Returns the result of the equality comparison. + * + * \return A valid handle if no error occurs during the comparison. + */ +DART_EXPORT Dart_Handle Dart_ObjectEquals(Dart_Handle obj1, + Dart_Handle obj2, + bool* equal); + +/** + * Is this object an instance of some type? + * + * The result of the test is returned through the 'instanceof' parameter. + * The return value itself is used to indicate success or failure. + * + * \param object An object. + * \param type A type. + * \param instanceof Return true if 'object' is an instance of type 'type'. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_ObjectIsType(Dart_Handle object, + Dart_Handle type, + bool* instanceof); + +/** + * Query object type. + * + * \param object Some Object. + * + * \return true if Object is of the specified type. + */ +DART_EXPORT bool Dart_IsInstance(Dart_Handle object); +DART_EXPORT bool Dart_IsNumber(Dart_Handle object); +DART_EXPORT bool Dart_IsInteger(Dart_Handle object); +DART_EXPORT bool Dart_IsDouble(Dart_Handle object); +DART_EXPORT bool Dart_IsBoolean(Dart_Handle object); +DART_EXPORT bool Dart_IsString(Dart_Handle object); +DART_EXPORT bool Dart_IsStringLatin1(Dart_Handle object); /* (ISO-8859-1) */ +DART_EXPORT bool Dart_IsExternalString(Dart_Handle object); +DART_EXPORT bool Dart_IsList(Dart_Handle object); +DART_EXPORT bool Dart_IsMap(Dart_Handle object); +DART_EXPORT bool Dart_IsLibrary(Dart_Handle object); +DART_EXPORT bool Dart_IsType(Dart_Handle handle); +DART_EXPORT bool Dart_IsFunction(Dart_Handle handle); +DART_EXPORT bool Dart_IsVariable(Dart_Handle handle); +DART_EXPORT bool Dart_IsTypeVariable(Dart_Handle handle); +DART_EXPORT bool Dart_IsClosure(Dart_Handle object); +DART_EXPORT bool Dart_IsTypedData(Dart_Handle object); +DART_EXPORT bool Dart_IsByteBuffer(Dart_Handle object); +DART_EXPORT bool Dart_IsFuture(Dart_Handle object); + +/* + * ========= + * Instances + * ========= + */ + +/* + * For the purposes of the embedding api, not all objects returned are + * Dart language objects. Within the api, we use the term 'Instance' + * to indicate handles which refer to true Dart language objects. + * + * TODO(turnidge): Reorganize the "Object" section above, pulling down + * any functions that more properly belong here. */ + +/** + * Gets the type of a Dart language object. + * + * \param instance Some Dart object. + * + * \return If no error occurs, the type is returned. Otherwise an + * error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_InstanceGetType(Dart_Handle instance); + +/** + * Returns the name for the provided class type. + * + * \return A valid string handle if no error occurs during the + * operation. + */ +DART_EXPORT Dart_Handle Dart_ClassName(Dart_Handle cls_type); + +/** + * Returns the name for the provided function or method. + * + * \return A valid string handle if no error occurs during the + * operation. + */ +DART_EXPORT Dart_Handle Dart_FunctionName(Dart_Handle function); + +/** + * Returns a handle to the owner of a function. + * + * The owner of an instance method or a static method is its defining + * class. The owner of a top-level function is its defining + * library. The owner of the function of a non-implicit closure is the + * function of the method or closure that defines the non-implicit + * closure. + * + * \return A valid handle to the owner of the function, or an error + * handle if the argument is not a valid handle to a function. + */ +DART_EXPORT Dart_Handle Dart_FunctionOwner(Dart_Handle function); + +/** + * Determines whether a function handle referes to a static function + * of method. + * + * For the purposes of the embedding API, a top-level function is + * implicitly declared static. + * + * \param function A handle to a function or method declaration. + * \param is_static Returns whether the function or method is declared static. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_FunctionIsStatic(Dart_Handle function, + bool* is_static); + +/** + * Is this object a closure resulting from a tear-off (closurized method)? + * + * Returns true for closures produced when an ordinary method is accessed + * through a getter call. Returns false otherwise, in particular for closures + * produced from local function declarations. + * + * \param object Some Object. + * + * \return true if Object is a tear-off. + */ +DART_EXPORT bool Dart_IsTearOff(Dart_Handle object); + +/** + * Retrieves the function of a closure. + * + * \return A handle to the function of the closure, or an error handle if the + * argument is not a closure. + */ +DART_EXPORT Dart_Handle Dart_ClosureFunction(Dart_Handle closure); + +/** + * Returns a handle to the library which contains class. + * + * \return A valid handle to the library with owns class, null if the class + * has no library or an error handle if the argument is not a valid handle + * to a class type. + */ +DART_EXPORT Dart_Handle Dart_ClassLibrary(Dart_Handle cls_type); + +/* + * ============================= + * Numbers, Integers and Doubles + * ============================= + */ + +/** + * Does this Integer fit into a 64-bit signed integer? + * + * \param integer An integer. + * \param fits Returns true if the integer fits into a 64-bit signed integer. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_IntegerFitsIntoInt64(Dart_Handle integer, + bool* fits); + +/** + * Does this Integer fit into a 64-bit unsigned integer? + * + * \param integer An integer. + * \param fits Returns true if the integer fits into a 64-bit unsigned integer. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_IntegerFitsIntoUint64(Dart_Handle integer, + bool* fits); + +/** + * Returns an Integer with the provided value. + * + * \param value The value of the integer. + * + * \return The Integer object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewInteger(int64_t value); + +/** + * Returns an Integer with the provided value. + * + * \param value The unsigned value of the integer. + * + * \return The Integer object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewIntegerFromUint64(uint64_t value); + +/** + * Returns an Integer with the provided value. + * + * \param value The value of the integer represented as a C string + * containing a hexadecimal number. + * + * \return The Integer object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewIntegerFromHexCString(const char* value); + +/** + * Gets the value of an Integer. + * + * The integer must fit into a 64-bit signed integer, otherwise an error occurs. + * + * \param integer An Integer. + * \param value Returns the value of the Integer. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_IntegerToInt64(Dart_Handle integer, + int64_t* value); + +/** + * Gets the value of an Integer. + * + * The integer must fit into a 64-bit unsigned integer, otherwise an + * error occurs. + * + * \param integer An Integer. + * \param value Returns the value of the Integer. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_IntegerToUint64(Dart_Handle integer, + uint64_t* value); + +/** + * Gets the value of an integer as a hexadecimal C string. + * + * \param integer An Integer. + * \param value Returns the value of the Integer as a hexadecimal C + * string. This C string is scope allocated and is only valid until + * the next call to Dart_ExitScope. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_IntegerToHexCString(Dart_Handle integer, + const char** value); + +/** + * Returns a Double with the provided value. + * + * \param value A double. + * + * \return The Double object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewDouble(double value); + +/** + * Gets the value of a Double + * + * \param double_obj A Double + * \param value Returns the value of the Double. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_DoubleValue(Dart_Handle double_obj, double* value); + +/** + * Returns a closure of static function 'function_name' in the class 'class_name' + * in the exported namespace of specified 'library'. + * + * \param library Library object + * \param cls_type Type object representing a Class + * \param function_name Name of the static function in the class + * + * \return A valid Dart instance if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_GetStaticMethodClosure(Dart_Handle library, + Dart_Handle cls_type, + Dart_Handle function_name); + +/* + * ======== + * Booleans + * ======== + */ + +/** + * Returns the True object. + * + * Requires there to be a current isolate. + * + * \return A handle to the True object. + */ +DART_EXPORT Dart_Handle Dart_True(); + +/** + * Returns the False object. + * + * Requires there to be a current isolate. + * + * \return A handle to the False object. + */ +DART_EXPORT Dart_Handle Dart_False(); + +/** + * Returns a Boolean with the provided value. + * + * \param value true or false. + * + * \return The Boolean object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewBoolean(bool value); + +/** + * Gets the value of a Boolean + * + * \param boolean_obj A Boolean + * \param value Returns the value of the Boolean. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_BooleanValue(Dart_Handle boolean_obj, bool* value); + +/* + * ======= + * Strings + * ======= + */ + +/** + * Gets the length of a String. + * + * \param str A String. + * \param length Returns the length of the String. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringLength(Dart_Handle str, intptr_t* length); + +/** + * Returns a String built from the provided C string + * (There is an implicit assumption that the C string passed in contains + * UTF-8 encoded characters and '\0' is considered as a termination + * character). + * + * \param value A C String + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewStringFromCString(const char* str); +/* TODO(turnidge): Document what happens when we run out of memory + * during this call. */ + +/** + * Returns a String built from an array of UTF-8 encoded characters. + * + * \param utf8_array An array of UTF-8 encoded characters. + * \param length The length of the codepoints array. + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewStringFromUTF8(const uint8_t* utf8_array, + intptr_t length); + +/** + * Returns a String built from an array of UTF-16 encoded characters. + * + * \param utf16_array An array of UTF-16 encoded characters. + * \param length The length of the codepoints array. + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewStringFromUTF16(const uint16_t* utf16_array, + intptr_t length); + +/** + * Returns a String built from an array of UTF-32 encoded characters. + * + * \param utf32_array An array of UTF-32 encoded characters. + * \param length The length of the codepoints array. + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewStringFromUTF32(const int32_t* utf32_array, + intptr_t length); + +/** + * Returns a String which references an external array of + * Latin-1 (ISO-8859-1) encoded characters. + * + * \param latin1_array Array of Latin-1 encoded characters. This must not move. + * \param length The length of the characters array. + * \param peer An external pointer to associate with this string. + * \param external_allocation_size The number of externally allocated + * bytes for peer. Used to inform the garbage collector. + * \param callback A callback to be called when this string is finalized. + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle +Dart_NewExternalLatin1String(const uint8_t* latin1_array, + intptr_t length, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); + +/** + * Returns a String which references an external array of UTF-16 encoded + * characters. + * + * \param utf16_array An array of UTF-16 encoded characters. This must not move. + * \param length The length of the characters array. + * \param peer An external pointer to associate with this string. + * \param external_allocation_size The number of externally allocated + * bytes for peer. Used to inform the garbage collector. + * \param callback A callback to be called when this string is finalized. + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle +Dart_NewExternalUTF16String(const uint16_t* utf16_array, + intptr_t length, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); + +/** + * Gets the C string representation of a String. + * (It is a sequence of UTF-8 encoded values with a '\0' termination.) + * + * \param str A string. + * \param cstr Returns the String represented as a C string. + * This C string is scope allocated and is only valid until + * the next call to Dart_ExitScope. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringToCString(Dart_Handle str, + const char** cstr); + +/** + * Gets a UTF-8 encoded representation of a String. + * + * Any unpaired surrogate code points in the string will be converted as + * replacement characters (U+FFFD, 0xEF 0xBF 0xBD in UTF-8). If you need + * to preserve unpaired surrogates, use the Dart_StringToUTF16 function. + * + * \param str A string. + * \param utf8_array Returns the String represented as UTF-8 code + * units. This UTF-8 array is scope allocated and is only valid + * until the next call to Dart_ExitScope. + * \param length Used to return the length of the array which was + * actually used. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringToUTF8(Dart_Handle str, + uint8_t** utf8_array, + intptr_t* length); + +/** + * Gets the data corresponding to the string object. This function returns + * the data only for Latin-1 (ISO-8859-1) string objects. For all other + * string objects it returns an error. + * + * \param str A string. + * \param latin1_array An array allocated by the caller, used to return + * the string data. + * \param length Used to pass in the length of the provided array. + * Used to return the length of the array which was actually used. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringToLatin1(Dart_Handle str, + uint8_t* latin1_array, + intptr_t* length); + +/** + * Gets the UTF-16 encoded representation of a string. + * + * \param str A string. + * \param utf16_array An array allocated by the caller, used to return + * the array of UTF-16 encoded characters. + * \param length Used to pass in the length of the provided array. + * Used to return the length of the array which was actually used. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringToUTF16(Dart_Handle str, + uint16_t* utf16_array, + intptr_t* length); + +/** + * Gets the storage size in bytes of a String. + * + * \param str A String. + * \param length Returns the storage size in bytes of the String. + * This is the size in bytes needed to store the String. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringStorageSize(Dart_Handle str, intptr_t* size); + +/** + * Retrieves some properties associated with a String. + * Properties retrieved are: + * - character size of the string (one or two byte) + * - length of the string + * - peer pointer of string if it is an external string. + * \param str A String. + * \param char_size Returns the character size of the String. + * \param str_len Returns the length of the String. + * \param peer Returns the peer pointer associated with the String or 0 if + * there is no peer pointer for it. + * \return Success if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_StringGetProperties(Dart_Handle str, + intptr_t* char_size, + intptr_t* str_len, + void** peer); + +/* + * ===== + * Lists + * ===== + */ + +/** + * Returns a List of the desired length. + * + * \param length The length of the list. + * + * \return The List object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewList(intptr_t length); + +typedef enum { + Dart_CoreType_Dynamic, + Dart_CoreType_Int, + Dart_CoreType_String, +} Dart_CoreType_Id; + +// TODO(bkonyi): convert this to use nullable types once NNBD is enabled. +/** + * Returns a List of the desired length with the desired legacy element type. + * + * \param element_type_id The type of elements of the list. + * \param length The length of the list. + * + * \return The List object if no error occurs. Otherwise returns an error + * handle. + */ +DART_EXPORT Dart_Handle Dart_NewListOf(Dart_CoreType_Id element_type_id, + intptr_t length); + +/** + * Returns a List of the desired length with the desired element type. + * + * \param element_type Handle to a nullable type object. E.g., from + * Dart_GetType or Dart_GetNullableType. + * + * \param length The length of the list. + * + * \return The List object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewListOfType(Dart_Handle element_type, + intptr_t length); + +/** + * Returns a List of the desired length with the desired element type, filled + * with the provided object. + * + * \param element_type Handle to a type object. E.g., from Dart_GetType. + * + * \param fill_object Handle to an object of type 'element_type' that will be + * used to populate the list. This parameter can only be Dart_Null() if the + * length of the list is 0 or 'element_type' is a nullable type. + * + * \param length The length of the list. + * + * \return The List object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewListOfTypeFilled(Dart_Handle element_type, + Dart_Handle fill_object, + intptr_t length); + +/** + * Gets the length of a List. + * + * May generate an unhandled exception error. + * + * \param list A List. + * \param length Returns the length of the List. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_ListLength(Dart_Handle list, intptr_t* length); + +/** + * Gets the Object at some index of a List. + * + * If the index is out of bounds, an error occurs. + * + * May generate an unhandled exception error. + * + * \param list A List. + * \param index A valid index into the List. + * + * \return The Object in the List at the specified index if no error + * occurs. Otherwise returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_ListGetAt(Dart_Handle list, intptr_t index); + +/** +* Gets a range of Objects from a List. +* +* If any of the requested index values are out of bounds, an error occurs. +* +* May generate an unhandled exception error. +* +* \param list A List. +* \param offset The offset of the first item to get. +* \param length The number of items to get. +* \param result A pointer to fill with the objects. +* +* \return Success if no error occurs during the operation. +*/ +DART_EXPORT Dart_Handle Dart_ListGetRange(Dart_Handle list, + intptr_t offset, + intptr_t length, + Dart_Handle* result); + +/** + * Sets the Object at some index of a List. + * + * If the index is out of bounds, an error occurs. + * + * May generate an unhandled exception error. + * + * \param array A List. + * \param index A valid index into the List. + * \param value The Object to put in the List. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_ListSetAt(Dart_Handle list, + intptr_t index, + Dart_Handle value); + +/** + * May generate an unhandled exception error. + */ +DART_EXPORT Dart_Handle Dart_ListGetAsBytes(Dart_Handle list, + intptr_t offset, + uint8_t* native_array, + intptr_t length); + +/** + * May generate an unhandled exception error. + */ +DART_EXPORT Dart_Handle Dart_ListSetAsBytes(Dart_Handle list, + intptr_t offset, + const uint8_t* native_array, + intptr_t length); + +/* + * ==== + * Maps + * ==== + */ + +/** + * Gets the Object at some key of a Map. + * + * May generate an unhandled exception error. + * + * \param map A Map. + * \param key An Object. + * + * \return The value in the map at the specified key, null if the map does not + * contain the key, or an error handle. + */ +DART_EXPORT Dart_Handle Dart_MapGetAt(Dart_Handle map, Dart_Handle key); + +/** + * Returns whether the Map contains a given key. + * + * May generate an unhandled exception error. + * + * \param map A Map. + * + * \return A handle on a boolean indicating whether map contains the key. + * Otherwise returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_MapContainsKey(Dart_Handle map, Dart_Handle key); + +/** + * Gets the list of keys of a Map. + * + * May generate an unhandled exception error. + * + * \param map A Map. + * + * \return The list of key Objects if no error occurs. Otherwise returns an + * error handle. + */ +DART_EXPORT Dart_Handle Dart_MapKeys(Dart_Handle map); + +/* + * ========== + * Typed Data + * ========== + */ + +typedef enum { + Dart_TypedData_kByteData = 0, + Dart_TypedData_kInt8, + Dart_TypedData_kUint8, + Dart_TypedData_kUint8Clamped, + Dart_TypedData_kInt16, + Dart_TypedData_kUint16, + Dart_TypedData_kInt32, + Dart_TypedData_kUint32, + Dart_TypedData_kInt64, + Dart_TypedData_kUint64, + Dart_TypedData_kFloat32, + Dart_TypedData_kFloat64, + Dart_TypedData_kInt32x4, + Dart_TypedData_kFloat32x4, + Dart_TypedData_kFloat64x2, + Dart_TypedData_kInvalid +} Dart_TypedData_Type; + +/** + * Return type if this object is a TypedData object. + * + * \return kInvalid if the object is not a TypedData object or the appropriate + * Dart_TypedData_Type. + */ +DART_EXPORT Dart_TypedData_Type Dart_GetTypeOfTypedData(Dart_Handle object); + +/** + * Return type if this object is an external TypedData object. + * + * \return kInvalid if the object is not an external TypedData object or + * the appropriate Dart_TypedData_Type. + */ +DART_EXPORT Dart_TypedData_Type +Dart_GetTypeOfExternalTypedData(Dart_Handle object); + +/** + * Returns a TypedData object of the desired length and type. + * + * \param type The type of the TypedData object. + * \param length The length of the TypedData object (length in type units). + * + * \return The TypedData object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewTypedData(Dart_TypedData_Type type, + intptr_t length); + +/** + * Returns a TypedData object which references an external data array. + * + * \param type The type of the data array. + * \param data A data array. This array must not move. + * \param length The length of the data array (length in type units). + * + * \return The TypedData object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewExternalTypedData(Dart_TypedData_Type type, + void* data, + intptr_t length); + +/** + * Returns a TypedData object which references an external data array. + * + * \param type The type of the data array. + * \param data A data array. This array must not move. + * \param length The length of the data array (length in type units). + * \param peer A pointer to a native object or NULL. This value is + * provided to callback when it is invoked. + * \param external_allocation_size The number of externally allocated + * bytes for peer. Used to inform the garbage collector. + * \param callback A function pointer that will be invoked sometime + * after the object is garbage collected, unless the handle has been deleted. + * A valid callback needs to be specified it cannot be NULL. + * + * \return The TypedData object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle +Dart_NewExternalTypedDataWithFinalizer(Dart_TypedData_Type type, + void* data, + intptr_t length, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); + +/** + * Returns a ByteBuffer object for the typed data. + * + * \param type_data The TypedData object. + * + * \return The ByteBuffer object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewByteBuffer(Dart_Handle typed_data); + +/** + * Acquires access to the internal data address of a TypedData object. + * + * \param object The typed data object whose internal data address is to + * be accessed. + * \param type The type of the object is returned here. + * \param data The internal data address is returned here. + * \param len Size of the typed array is returned here. + * + * Notes: + * When the internal address of the object is acquired any calls to a + * Dart API function that could potentially allocate an object or run + * any Dart code will return an error. + * + * Any Dart API functions for accessing the data should not be called + * before the corresponding release. In particular, the object should + * not be acquired again before its release. This leads to undefined + * behavior. + * + * \return Success if the internal data address is acquired successfully. + * Otherwise, returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_TypedDataAcquireData(Dart_Handle object, + Dart_TypedData_Type* type, + void** data, + intptr_t* len); + +/** + * Releases access to the internal data address that was acquired earlier using + * Dart_TypedDataAcquireData. + * + * \param object The typed data object whose internal data address is to be + * released. + * + * \return Success if the internal data address is released successfully. + * Otherwise, returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_TypedDataReleaseData(Dart_Handle object); + +/** + * Returns the TypedData object associated with the ByteBuffer object. + * + * \param byte_buffer The ByteBuffer object. + * + * \return The TypedData object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_GetDataFromByteBuffer(Dart_Handle byte_buffer); + +/* + * ============================================================ + * Invoking Constructors, Methods, Closures and Field accessors + * ============================================================ + */ + +/** + * Invokes a constructor, creating a new object. + * + * This function allows hidden constructors (constructors with leading + * underscores) to be called. + * + * \param type Type of object to be constructed. + * \param constructor_name The name of the constructor to invoke. Use + * Dart_Null() or Dart_EmptyString() to invoke the unnamed constructor. + * This name should not include the name of the class. + * \param number_of_arguments Size of the arguments array. + * \param arguments An array of arguments to the constructor. + * + * \return If the constructor is called and completes successfully, + * then the new object. If an error occurs during execution, then an + * error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_New(Dart_Handle type, + Dart_Handle constructor_name, + int number_of_arguments, + Dart_Handle* arguments); + +/** + * Allocate a new object without invoking a constructor. + * + * \param type The type of an object to be allocated. + * + * \return The new object. If an error occurs during execution, then an + * error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_Allocate(Dart_Handle type); + +/** + * Allocate a new object without invoking a constructor, and sets specified + * native fields. + * + * \param type The type of an object to be allocated. + * \param num_native_fields The number of native fields to set. + * \param native_fields An array containing the value of native fields. + * + * \return The new object. If an error occurs during execution, then an + * error handle is returned. + */ +DART_EXPORT Dart_Handle +Dart_AllocateWithNativeFields(Dart_Handle type, + intptr_t num_native_fields, + const intptr_t* native_fields); + +/** + * Invokes a method or function. + * + * The 'target' parameter may be an object, type, or library. If + * 'target' is an object, then this function will invoke an instance + * method. If 'target' is a type, then this function will invoke a + * static method. If 'target' is a library, then this function will + * invoke a top-level function from that library. + * NOTE: This API call cannot be used to invoke methods of a type object. + * + * This function ignores visibility (leading underscores in names). + * + * May generate an unhandled exception error. + * + * \param target An object, type, or library. + * \param name The name of the function or method to invoke. + * \param number_of_arguments Size of the arguments array. + * \param arguments An array of arguments to the function. + * + * \return If the function or method is called and completes + * successfully, then the return value is returned. If an error + * occurs during execution, then an error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_Invoke(Dart_Handle target, + Dart_Handle name, + int number_of_arguments, + Dart_Handle* arguments); +/* TODO(turnidge): Document how to invoke operators. */ + +/** + * Invokes a Closure with the given arguments. + * + * May generate an unhandled exception error. + * + * \return If no error occurs during execution, then the result of + * invoking the closure is returned. If an error occurs during + * execution, then an error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_InvokeClosure(Dart_Handle closure, + int number_of_arguments, + Dart_Handle* arguments); + +/** + * Invokes a Generative Constructor on an object that was previously + * allocated using Dart_Allocate/Dart_AllocateWithNativeFields. + * + * The 'target' parameter must be an object. + * + * This function ignores visibility (leading underscores in names). + * + * May generate an unhandled exception error. + * + * \param target An object. + * \param name The name of the constructor to invoke. + * Use Dart_Null() or Dart_EmptyString() to invoke the unnamed constructor. + * \param number_of_arguments Size of the arguments array. + * \param arguments An array of arguments to the function. + * + * \return If the constructor is called and completes + * successfully, then the object is returned. If an error + * occurs during execution, then an error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_InvokeConstructor(Dart_Handle object, + Dart_Handle name, + int number_of_arguments, + Dart_Handle* arguments); + +/** + * Gets the value of a field. + * + * The 'container' parameter may be an object, type, or library. If + * 'container' is an object, then this function will access an + * instance field. If 'container' is a type, then this function will + * access a static field. If 'container' is a library, then this + * function will access a top-level variable. + * NOTE: This API call cannot be used to access fields of a type object. + * + * This function ignores field visibility (leading underscores in names). + * + * May generate an unhandled exception error. + * + * \param container An object, type, or library. + * \param name A field name. + * + * \return If no error occurs, then the value of the field is + * returned. Otherwise an error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_GetField(Dart_Handle container, Dart_Handle name); + +/** + * Sets the value of a field. + * + * The 'container' parameter may actually be an object, type, or + * library. If 'container' is an object, then this function will + * access an instance field. If 'container' is a type, then this + * function will access a static field. If 'container' is a library, + * then this function will access a top-level variable. + * NOTE: This API call cannot be used to access fields of a type object. + * + * This function ignores field visibility (leading underscores in names). + * + * May generate an unhandled exception error. + * + * \param container An object, type, or library. + * \param name A field name. + * \param value The new field value. + * + * \return A valid handle if no error occurs. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_SetField(Dart_Handle container, Dart_Handle name, Dart_Handle value); + +/* + * ========== + * Exceptions + * ========== + */ + +/* + * TODO(turnidge): Remove these functions from the api and replace all + * uses with Dart_NewUnhandledExceptionError. */ + +/** + * Throws an exception. + * + * This function causes a Dart language exception to be thrown. This + * will proceed in the standard way, walking up Dart frames until an + * appropriate 'catch' block is found, executing 'finally' blocks, + * etc. + * + * If an error handle is passed into this function, the error is + * propagated immediately. See Dart_PropagateError for a discussion + * of error propagation. + * + * If successful, this function does not return. Note that this means + * that the destructors of any stack-allocated C++ objects will not be + * called. If there are no Dart frames on the stack, an error occurs. + * + * \return An error handle if the exception was not thrown. + * Otherwise the function does not return. + */ +DART_EXPORT Dart_Handle Dart_ThrowException(Dart_Handle exception); + +/** + * Rethrows an exception. + * + * Rethrows an exception, unwinding all dart frames on the stack. If + * successful, this function does not return. Note that this means + * that the destructors of any stack-allocated C++ objects will not be + * called. If there are no Dart frames on the stack, an error occurs. + * + * \return An error handle if the exception was not thrown. + * Otherwise the function does not return. + */ +DART_EXPORT Dart_Handle Dart_ReThrowException(Dart_Handle exception, + Dart_Handle stacktrace); + +/* + * =========================== + * Native fields and functions + * =========================== + */ + +/** + * Gets the number of native instance fields in an object. + */ +DART_EXPORT Dart_Handle Dart_GetNativeInstanceFieldCount(Dart_Handle obj, + int* count); + +/** + * Gets the value of a native field. + * + * TODO(turnidge): Document. + */ +DART_EXPORT Dart_Handle Dart_GetNativeInstanceField(Dart_Handle obj, + int index, + intptr_t* value); + +/** + * Sets the value of a native field. + * + * TODO(turnidge): Document. + */ +DART_EXPORT Dart_Handle Dart_SetNativeInstanceField(Dart_Handle obj, + int index, + intptr_t value); + +/** + * The arguments to a native function. + * + * This object is passed to a native function to represent its + * arguments and return value. It allows access to the arguments to a + * native function by index. It also allows the return value of a + * native function to be set. + */ +typedef struct _Dart_NativeArguments* Dart_NativeArguments; + +/** + * Extracts current isolate group data from the native arguments structure. + */ +DART_EXPORT void* Dart_GetNativeIsolateGroupData(Dart_NativeArguments args); + +typedef enum { + Dart_NativeArgument_kBool = 0, + Dart_NativeArgument_kInt32, + Dart_NativeArgument_kUint32, + Dart_NativeArgument_kInt64, + Dart_NativeArgument_kUint64, + Dart_NativeArgument_kDouble, + Dart_NativeArgument_kString, + Dart_NativeArgument_kInstance, + Dart_NativeArgument_kNativeFields, +} Dart_NativeArgument_Type; + +typedef struct _Dart_NativeArgument_Descriptor { + uint8_t type; + uint8_t index; +} Dart_NativeArgument_Descriptor; + +typedef union _Dart_NativeArgument_Value { + bool as_bool; + int32_t as_int32; + uint32_t as_uint32; + int64_t as_int64; + uint64_t as_uint64; + double as_double; + struct { + Dart_Handle dart_str; + void* peer; + } as_string; + struct { + intptr_t num_fields; + intptr_t* values; + } as_native_fields; + Dart_Handle as_instance; +} Dart_NativeArgument_Value; + +enum { + kNativeArgNumberPos = 0, + kNativeArgNumberSize = 8, + kNativeArgTypePos = kNativeArgNumberPos + kNativeArgNumberSize, + kNativeArgTypeSize = 8, +}; + +#define BITMASK(size) ((1 << size) - 1) +#define DART_NATIVE_ARG_DESCRIPTOR(type, position) \ + (((type & BITMASK(kNativeArgTypeSize)) << kNativeArgTypePos) | \ + (position & BITMASK(kNativeArgNumberSize))) + +/** + * Gets the native arguments based on the types passed in and populates + * the passed arguments buffer with appropriate native values. + * + * \param args the Native arguments block passed into the native call. + * \param num_arguments length of argument descriptor array and argument + * values array passed in. + * \param arg_descriptors an array that describes the arguments that + * need to be retrieved. For each argument to be retrieved the descriptor + * contains the argument number (0, 1 etc.) and the argument type + * described using Dart_NativeArgument_Type, e.g: + * DART_NATIVE_ARG_DESCRIPTOR(Dart_NativeArgument_kBool, 1) indicates + * that the first argument is to be retrieved and it should be a boolean. + * \param arg_values array into which the native arguments need to be + * extracted into, the array is allocated by the caller (it could be + * stack allocated to avoid the malloc/free performance overhead). + * + * \return Success if all the arguments could be extracted correctly, + * returns an error handle if there were any errors while extracting the + * arguments (mismatched number of arguments, incorrect types, etc.). + */ +DART_EXPORT Dart_Handle +Dart_GetNativeArguments(Dart_NativeArguments args, + int num_arguments, + const Dart_NativeArgument_Descriptor* arg_descriptors, + Dart_NativeArgument_Value* arg_values); + +/** + * Gets the native argument at some index. + */ +DART_EXPORT Dart_Handle Dart_GetNativeArgument(Dart_NativeArguments args, + int index); +/* TODO(turnidge): Specify the behavior of an out-of-bounds access. */ + +/** + * Gets the number of native arguments. + */ +DART_EXPORT int Dart_GetNativeArgumentCount(Dart_NativeArguments args); + +/** + * Gets all the native fields of the native argument at some index. + * \param args Native arguments structure. + * \param arg_index Index of the desired argument in the structure above. + * \param num_fields size of the intptr_t array 'field_values' passed in. + * \param field_values intptr_t array in which native field values are returned. + * \return Success if the native fields where copied in successfully. Otherwise + * returns an error handle. On success the native field values are copied + * into the 'field_values' array, if the argument at 'arg_index' is a + * null object then 0 is copied as the native field values into the + * 'field_values' array. + */ +DART_EXPORT Dart_Handle +Dart_GetNativeFieldsOfArgument(Dart_NativeArguments args, + int arg_index, + int num_fields, + intptr_t* field_values); + +/** + * Gets the native field of the receiver. + */ +DART_EXPORT Dart_Handle Dart_GetNativeReceiver(Dart_NativeArguments args, + intptr_t* value); + +/** + * Gets a string native argument at some index. + * \param args Native arguments structure. + * \param arg_index Index of the desired argument in the structure above. + * \param peer Returns the peer pointer if the string argument has one. + * \return Success if the string argument has a peer, if it does not + * have a peer then the String object is returned. Otherwise returns + * an error handle (argument is not a String object). + */ +DART_EXPORT Dart_Handle Dart_GetNativeStringArgument(Dart_NativeArguments args, + int arg_index, + void** peer); + +/** + * Gets an integer native argument at some index. + * \param args Native arguments structure. + * \param arg_index Index of the desired argument in the structure above. + * \param value Returns the integer value if the argument is an Integer. + * \return Success if no error occurs. Otherwise returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_GetNativeIntegerArgument(Dart_NativeArguments args, + int index, + int64_t* value); + +/** + * Gets a boolean native argument at some index. + * \param args Native arguments structure. + * \param arg_index Index of the desired argument in the structure above. + * \param value Returns the boolean value if the argument is a Boolean. + * \return Success if no error occurs. Otherwise returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_GetNativeBooleanArgument(Dart_NativeArguments args, + int index, + bool* value); + +/** + * Gets a double native argument at some index. + * \param args Native arguments structure. + * \param arg_index Index of the desired argument in the structure above. + * \param value Returns the double value if the argument is a double. + * \return Success if no error occurs. Otherwise returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_GetNativeDoubleArgument(Dart_NativeArguments args, + int index, + double* value); + +/** + * Sets the return value for a native function. + * + * If retval is an Error handle, then error will be propagated once + * the native functions exits. See Dart_PropagateError for a + * discussion of how different types of errors are propagated. + */ +DART_EXPORT void Dart_SetReturnValue(Dart_NativeArguments args, + Dart_Handle retval); + +DART_EXPORT void Dart_SetWeakHandleReturnValue(Dart_NativeArguments args, + Dart_WeakPersistentHandle rval); + +DART_EXPORT void Dart_SetBooleanReturnValue(Dart_NativeArguments args, + bool retval); + +DART_EXPORT void Dart_SetIntegerReturnValue(Dart_NativeArguments args, + int64_t retval); + +DART_EXPORT void Dart_SetDoubleReturnValue(Dart_NativeArguments args, + double retval); + +/** + * A native function. + */ +typedef void (*Dart_NativeFunction)(Dart_NativeArguments arguments); + +/** + * Native entry resolution callback. + * + * For libraries and scripts which have native functions, the embedder + * can provide a native entry resolver. This callback is used to map a + * name/arity to a Dart_NativeFunction. If no function is found, the + * callback should return NULL. + * + * The parameters to the native resolver function are: + * \param name a Dart string which is the name of the native function. + * \param num_of_arguments is the number of arguments expected by the + * native function. + * \param auto_setup_scope is a boolean flag that can be set by the resolver + * to indicate if this function needs a Dart API scope (see Dart_EnterScope/ + * Dart_ExitScope) to be setup automatically by the VM before calling into + * the native function. By default most native functions would require this + * to be true but some light weight native functions which do not call back + * into the VM through the Dart API may not require a Dart scope to be + * setup automatically. + * + * \return A valid Dart_NativeFunction which resolves to a native entry point + * for the native function. + * + * See Dart_SetNativeResolver. + */ +typedef Dart_NativeFunction (*Dart_NativeEntryResolver)(Dart_Handle name, + int num_of_arguments, + bool* auto_setup_scope); +/* TODO(turnidge): Consider renaming to NativeFunctionResolver or + * NativeResolver. */ + +/** + * Native entry symbol lookup callback. + * + * For libraries and scripts which have native functions, the embedder + * can provide a callback for mapping a native entry to a symbol. This callback + * maps a native function entry PC to the native function name. If no native + * entry symbol can be found, the callback should return NULL. + * + * The parameters to the native reverse resolver function are: + * \param nf A Dart_NativeFunction. + * + * \return A const UTF-8 string containing the symbol name or NULL. + * + * See Dart_SetNativeResolver. + */ +typedef const uint8_t* (*Dart_NativeEntrySymbol)(Dart_NativeFunction nf); + +/* + * =========== + * Environment + * =========== + */ + +/** + * An environment lookup callback function. + * + * \param name The name of the value to lookup in the environment. + * + * \return A valid handle to a string if the name exists in the + * current environment or Dart_Null() if not. + */ +typedef Dart_Handle (*Dart_EnvironmentCallback)(Dart_Handle name); + +/** + * Sets the environment callback for the current isolate. This + * callback is used to lookup environment values by name in the + * current environment. This enables the embedder to supply values for + * the const constructors bool.fromEnvironment, int.fromEnvironment + * and String.fromEnvironment. + */ +DART_EXPORT Dart_Handle +Dart_SetEnvironmentCallback(Dart_EnvironmentCallback callback); + +/** + * Sets the callback used to resolve native functions for a library. + * + * \param library A library. + * \param resolver A native entry resolver. + * + * \return A valid handle if the native resolver was set successfully. + */ +DART_EXPORT Dart_Handle +Dart_SetNativeResolver(Dart_Handle library, + Dart_NativeEntryResolver resolver, + Dart_NativeEntrySymbol symbol); +/* TODO(turnidge): Rename to Dart_LibrarySetNativeResolver? */ + +/** + * Returns the callback used to resolve native functions for a library. + * + * \param library A library. + * \param resolver a pointer to a Dart_NativeEntryResolver + * + * \return A valid handle if the library was found. + */ +DART_EXPORT Dart_Handle +Dart_GetNativeResolver(Dart_Handle library, Dart_NativeEntryResolver* resolver); + +/** + * Returns the callback used to resolve native function symbols for a library. + * + * \param library A library. + * \param resolver a pointer to a Dart_NativeEntrySymbol. + * + * \return A valid handle if the library was found. + */ +DART_EXPORT Dart_Handle Dart_GetNativeSymbol(Dart_Handle library, + Dart_NativeEntrySymbol* resolver); + +/* + * ===================== + * Scripts and Libraries + * ===================== + */ + +typedef enum { + Dart_kCanonicalizeUrl = 0, + Dart_kImportTag, + Dart_kKernelTag, + Dart_kImportExtensionTag, +} Dart_LibraryTag; + +/** + * The library tag handler is a multi-purpose callback provided by the + * embedder to the Dart VM. The embedder implements the tag handler to + * provide the ability to load Dart scripts and imports. + * + * -- TAGS -- + * + * Dart_kCanonicalizeUrl + * + * This tag indicates that the embedder should canonicalize 'url' with + * respect to 'library'. For most embedders, the + * Dart_DefaultCanonicalizeUrl function is a sufficient implementation + * of this tag. The return value should be a string holding the + * canonicalized url. + * + * Dart_kImportTag + * + * This tag is used to load a library from IsolateMirror.loadUri. The embedder + * should call Dart_LoadLibraryFromKernel to provide the library to the VM. The + * return value should be an error or library (the result from + * Dart_LoadLibraryFromKernel). + * + * Dart_kKernelTag + * + * This tag is used to load the intermediate file (kernel) generated by + * the Dart front end. This tag is typically used when a 'hot-reload' + * of an application is needed and the VM is 'use dart front end' mode. + * The dart front end typically compiles all the scripts, imports and part + * files into one intermediate file hence we don't use the source/import or + * script tags. The return value should be an error or a TypedData containing + * the kernel bytes. + * + * Dart_kImportExtensionTag + * + * This tag is used to load an external import (shared object file). The + * extension path must have the scheme 'dart-ext:'. + */ +typedef Dart_Handle (*Dart_LibraryTagHandler)( + Dart_LibraryTag tag, + Dart_Handle library_or_package_map_url, + Dart_Handle url); + +/** + * Sets library tag handler for the current isolate. This handler is + * used to handle the various tags encountered while loading libraries + * or scripts in the isolate. + * + * \param handler Handler code to be used for handling the various tags + * encountered while loading libraries or scripts in the isolate. + * + * \return If no error occurs, the handler is set for the isolate. + * Otherwise an error handle is returned. + * + * TODO(turnidge): Document. + */ +DART_EXPORT Dart_Handle +Dart_SetLibraryTagHandler(Dart_LibraryTagHandler handler); + +/** + * Handles deferred loading requests. When this handler is invoked, it should + * eventually load the deferred loading unit with the given id and call + * Dart_DeferredLoadComplete or Dart_DeferredLoadCompleteError. It is + * recommended that the loading occur asynchronously, but it is permitted to + * call Dart_DeferredLoadComplete or Dart_DeferredLoadCompleteError before the + * handler returns. + * + * If an error is returned, it will be propogated through + * `prefix.loadLibrary()`. This is useful for synchronous + * implementations, which must propogate any unwind errors from + * Dart_DeferredLoadComplete or Dart_DeferredLoadComplete. Otherwise the handler + * should return a non-error such as `Dart_Null()`. + */ +typedef Dart_Handle (*Dart_DeferredLoadHandler)(intptr_t loading_unit_id); + +/** + * Sets the deferred load handler for the current isolate. This handler is + * used to handle loading deferred imports in an AppJIT or AppAOT program. + */ +DART_EXPORT Dart_Handle +Dart_SetDeferredLoadHandler(Dart_DeferredLoadHandler handler); + +/** + * Notifies the VM that a deferred load completed successfully. This function + * will eventually cause the corresponding `prefix.loadLibrary()` futures to + * complete. + * + * Requires the current isolate to be the same current isolate during the + * invocation of the Dart_DeferredLoadHandler. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_DeferredLoadComplete(intptr_t loading_unit_id, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions); + +/** + * Notifies the VM that a deferred load failed. This function + * will eventually cause the corresponding `prefix.loadLibrary()` futures to + * complete with an error. + * + * If `transient` is true, future invocations of `prefix.loadLibrary()` will + * trigger new load requests. If false, futures invocation will complete with + * the same error. + * + * Requires the current isolate to be the same current isolate during the + * invocation of the Dart_DeferredLoadHandler. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_DeferredLoadCompleteError(intptr_t loading_unit_id, + const char* error_message, + bool transient); + +/** + * Canonicalizes a url with respect to some library. + * + * The url is resolved with respect to the library's url and some url + * normalizations are performed. + * + * This canonicalization function should be sufficient for most + * embedders to implement the Dart_kCanonicalizeUrl tag. + * + * \param base_url The base url relative to which the url is + * being resolved. + * \param url The url being resolved and canonicalized. This + * parameter is a string handle. + * + * \return If no error occurs, a String object is returned. Otherwise + * an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_DefaultCanonicalizeUrl(Dart_Handle base_url, + Dart_Handle url); + +/** + * Loads the root library for the current isolate. + * + * Requires there to be no current root library. + * + * \param buffer A buffer which contains a kernel binary (see + * pkg/kernel/binary.md). Must remain valid until isolate group shutdown. + * \param buffer_size Length of the passed in buffer. + * + * \return A handle to the root library, or an error. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_LoadScriptFromKernel(const uint8_t* kernel_buffer, intptr_t kernel_size); + +/** + * Gets the library for the root script for the current isolate. + * + * If the root script has not yet been set for the current isolate, + * this function returns Dart_Null(). This function never returns an + * error handle. + * + * \return Returns the root Library for the current isolate or Dart_Null(). + */ +DART_EXPORT Dart_Handle Dart_RootLibrary(); + +/** + * Sets the root library for the current isolate. + * + * \return Returns an error handle if `library` is not a library handle. + */ +DART_EXPORT Dart_Handle Dart_SetRootLibrary(Dart_Handle library); + +/** + * Lookup or instantiate a legacy type by name and type arguments from a + * Library. + * + * \param library The library containing the class or interface. + * \param class_name The class name for the type. + * \param number_of_type_arguments Number of type arguments. + * For non parametric types the number of type arguments would be 0. + * \param type_arguments Pointer to an array of type arguments. + * For non parameteric types a NULL would be passed in for this argument. + * + * \return If no error occurs, the type is returned. + * Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_GetType(Dart_Handle library, + Dart_Handle class_name, + intptr_t number_of_type_arguments, + Dart_Handle* type_arguments); + +/** + * Lookup or instantiate a nullable type by name and type arguments from + * Library. + * + * \param library The library containing the class or interface. + * \param class_name The class name for the type. + * \param number_of_type_arguments Number of type arguments. + * For non parametric types the number of type arguments would be 0. + * \param type_arguments Pointer to an array of type arguments. + * For non parameteric types a NULL would be passed in for this argument. + * + * \return If no error occurs, the type is returned. + * Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_GetNullableType(Dart_Handle library, + Dart_Handle class_name, + intptr_t number_of_type_arguments, + Dart_Handle* type_arguments); + +/** + * Lookup or instantiate a non-nullable type by name and type arguments from + * Library. + * + * \param library The library containing the class or interface. + * \param class_name The class name for the type. + * \param number_of_type_arguments Number of type arguments. + * For non parametric types the number of type arguments would be 0. + * \param type_arguments Pointer to an array of type arguments. + * For non parameteric types a NULL would be passed in for this argument. + * + * \return If no error occurs, the type is returned. + * Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle +Dart_GetNonNullableType(Dart_Handle library, + Dart_Handle class_name, + intptr_t number_of_type_arguments, + Dart_Handle* type_arguments); + +/** + * Creates a nullable version of the provided type. + * + * \param type The type to be converted to a nullable type. + * + * \return If no error occurs, a nullable type is returned. + * Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_TypeToNullableType(Dart_Handle type); + +/** + * Creates a non-nullable version of the provided type. + * + * \param type The type to be converted to a non-nullable type. + * + * \return If no error occurs, a non-nullable type is returned. + * Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_TypeToNonNullableType(Dart_Handle type); + +/** + * A type's nullability. + * + * \param type A Dart type. + * \param result An out parameter containing the result of the check. True if + * the type is of the specified nullability, false otherwise. + * + * \return Returns an error handle if type is not of type Type. + */ +DART_EXPORT Dart_Handle Dart_IsNullableType(Dart_Handle type, bool* result); +DART_EXPORT Dart_Handle Dart_IsNonNullableType(Dart_Handle type, bool* result); +DART_EXPORT Dart_Handle Dart_IsLegacyType(Dart_Handle type, bool* result); + +/** + * Lookup a class or interface by name from a Library. + * + * \param library The library containing the class or interface. + * \param class_name The name of the class or interface. + * + * \return If no error occurs, the class or interface is + * returned. Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_GetClass(Dart_Handle library, + Dart_Handle class_name); +/* TODO(asiva): The above method needs to be removed once all uses + * of it are removed from the embedder code. */ + +/** + * Returns an import path to a Library, such as "file:///test.dart" or + * "dart:core". + */ +DART_EXPORT Dart_Handle Dart_LibraryUrl(Dart_Handle library); + +/** + * Returns a URL from which a Library was loaded. + */ +DART_EXPORT Dart_Handle Dart_LibraryResolvedUrl(Dart_Handle library); + +/** + * \return An array of libraries. + */ +DART_EXPORT Dart_Handle Dart_GetLoadedLibraries(); + +DART_EXPORT Dart_Handle Dart_LookupLibrary(Dart_Handle url); +/* TODO(turnidge): Consider returning Dart_Null() when the library is + * not found to distinguish that from a true error case. */ + +/** + * Report an loading error for the library. + * + * \param library The library that failed to load. + * \param error The Dart error instance containing the load error. + * + * \return If the VM handles the error, the return value is + * a null handle. If it doesn't handle the error, the error + * object is returned. + */ +DART_EXPORT Dart_Handle Dart_LibraryHandleError(Dart_Handle library, + Dart_Handle error); + +/** + * Called by the embedder to load a partial program. Does not set the root + * library. + * + * \param buffer A buffer which contains a kernel binary (see + * pkg/kernel/binary.md). Must remain valid until isolate shutdown. + * \param buffer_size Length of the passed in buffer. + * + * \return A handle to the main library of the compilation unit, or an error. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_LoadLibraryFromKernel(const uint8_t* kernel_buffer, + intptr_t kernel_buffer_size); + +/** + * Returns a flattened list of pairs. The first element in each pair is the + * importing library and and the second element is the imported library for each + * import in the isolate of a library whose URI's scheme is [scheme]. + * + * Requires there to be a current isolate. + * + * \return A handle to a list of flattened pairs of importer-importee. + */ +DART_EXPORT Dart_Handle Dart_GetImportsOfScheme(Dart_Handle scheme); + +/** + * Indicates that all outstanding load requests have been satisfied. + * This finalizes all the new classes loaded and optionally completes + * deferred library futures. + * + * Requires there to be a current isolate. + * + * \param complete_futures Specify true if all deferred library + * futures should be completed, false otherwise. + * + * \return Success if all classes have been finalized and deferred library + * futures are completed. Otherwise, returns an error. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_FinalizeLoading(bool complete_futures); + +/* + * ===== + * Peers + * ===== + */ + +/** + * The peer field is a lazily allocated field intended for storage of + * an uncommonly used values. Most instances types can have a peer + * field allocated. The exceptions are subtypes of Null, num, and + * bool. + */ + +/** + * Returns the value of peer field of 'object' in 'peer'. + * + * \param object An object. + * \param peer An out parameter that returns the value of the peer + * field. + * + * \return Returns an error if 'object' is a subtype of Null, num, or + * bool. + */ +DART_EXPORT Dart_Handle Dart_GetPeer(Dart_Handle object, void** peer); + +/** + * Sets the value of the peer field of 'object' to the value of + * 'peer'. + * + * \param object An object. + * \param peer A value to store in the peer field. + * + * \return Returns an error if 'object' is a subtype of Null, num, or + * bool. + */ +DART_EXPORT Dart_Handle Dart_SetPeer(Dart_Handle object, void* peer); + +/* + * ====== + * Kernel + * ====== + */ + +/** + * Experimental support for Dart to Kernel parser isolate. + * + * TODO(hausner): Document finalized interface. + * + */ + +// TODO(33433): Remove kernel service from the embedding API. + +typedef enum { + Dart_KernelCompilationStatus_Unknown = -1, + Dart_KernelCompilationStatus_Ok = 0, + Dart_KernelCompilationStatus_Error = 1, + Dart_KernelCompilationStatus_Crash = 2, +} Dart_KernelCompilationStatus; + +typedef struct { + Dart_KernelCompilationStatus status; + bool null_safety; + char* error; + uint8_t* kernel; + intptr_t kernel_size; +} Dart_KernelCompilationResult; + +DART_EXPORT bool Dart_IsKernelIsolate(Dart_Isolate isolate); +DART_EXPORT bool Dart_KernelIsolateIsRunning(); +DART_EXPORT Dart_Port Dart_KernelPort(); + +/** + * Compiles the given `script_uri` to a kernel file. + * + * \param platform_kernel A buffer containing the kernel of the platform (e.g. + * `vm_platform_strong.dill`). The VM does not take ownership of this memory. + * + * \param platform_kernel_size The length of the platform_kernel buffer. + * + * \return Returns the result of the compilation. + * + * On a successful compilation the returned [Dart_KernelCompilationResult] has + * a status of [Dart_KernelCompilationStatus_Ok] and the `kernel`/`kernel_size` + * fields are set. The caller takes ownership of the malloc()ed buffer. + * + * On a failed compilation the `error` might be set describing the reason for + * the failed compilation. The caller takes ownership of the malloc()ed + * error. + * + * Requires there to be a current isolate. + */ +DART_EXPORT Dart_KernelCompilationResult +Dart_CompileToKernel(const char* script_uri, + const uint8_t* platform_kernel, + const intptr_t platform_kernel_size, + bool incremental_compile, + const char* package_config); + +typedef struct { + const char* uri; + const char* source; +} Dart_SourceFile; + +DART_EXPORT Dart_KernelCompilationResult Dart_KernelListDependencies(); + +/** + * Sets the kernel buffer which will be used to load Dart SDK sources + * dynamically at runtime. + * + * \param platform_kernel A buffer containing kernel which has sources for the + * Dart SDK populated. Note: The VM does not take ownership of this memory. + * + * \param platform_kernel_size The length of the platform_kernel buffer. + */ +DART_EXPORT void Dart_SetDartLibrarySourcesKernel( + const uint8_t* platform_kernel, + const intptr_t platform_kernel_size); + +/** + * Detect the null safety opt-in status. + * + * When running from source, it is based on the opt-in status of `script_uri`. + * When running from a kernel buffer, it is based on the mode used when + * generating `kernel_buffer`. + * When running from an appJIT or AOT snapshot, it is based on the mode used + * when generating `snapshot_data`. + * + * \param script_uri Uri of the script that contains the source code + * + * \param package_config Uri of the package configuration file (either in format + * of .packages or .dart_tool/package_config.json) for the null safety + * detection to resolve package imports against. If this parameter is not + * passed the package resolution of the parent isolate should be used. + * + * \param original_working_directory current working directory when the VM + * process was launched, this is used to correctly resolve the path specified + * for package_config. + * + * \param snapshot_data + * + * \param snapshot_instructions Buffers containing a snapshot of the + * isolate or NULL if no snapshot is provided. If provided, the buffers must + * remain valid until the isolate shuts down. + * + * \param kernel_buffer + * + * \param kernel_buffer_size A buffer which contains a kernel/DIL program. Must + * remain valid until isolate shutdown. + * + * \return Returns true if the null safety is opted in by the input being + * run `script_uri`, `snapshot_data` or `kernel_buffer`. + * + */ +DART_EXPORT bool Dart_DetectNullSafety(const char* script_uri, + const char* package_config, + const char* original_working_directory, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions, + const uint8_t* kernel_buffer, + intptr_t kernel_buffer_size); + +#define DART_KERNEL_ISOLATE_NAME "kernel-service" + +/* + * ======= + * Service + * ======= + */ + +#define DART_VM_SERVICE_ISOLATE_NAME "vm-service" + +/** + * Returns true if isolate is the service isolate. + * + * \param isolate An isolate + * + * \return Returns true if 'isolate' is the service isolate. + */ +DART_EXPORT bool Dart_IsServiceIsolate(Dart_Isolate isolate); + +/** + * Writes the CPU profile to the timeline as a series of 'instant' events. + * + * Note that this is an expensive operation. + * + * \param main_port The main port of the Isolate whose profile samples to write. + * \param error An optional error, must be free()ed by caller. + * + * \return Returns true if the profile is successfully written and false + * otherwise. + */ +DART_EXPORT bool Dart_WriteProfileToTimeline(Dart_Port main_port, char** error); + +/* + * ==================== + * Compilation Feedback + * ==================== + */ + +/** + * Record all functions which have been compiled in the current isolate. + * + * \param buffer Returns a pointer to a buffer containing the trace. + * This buffer is scope allocated and is only valid until the next call to + * Dart_ExitScope. + * \param size Returns the size of the buffer. + * \return Returns an valid handle upon success. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_SaveCompilationTrace(uint8_t** buffer, intptr_t* buffer_length); + +/** + * Compile all functions from data from Dart_SaveCompilationTrace. Unlike JIT + * feedback, this data is fuzzy: loading does not need to happen in the exact + * program that was saved, the saver and loader do not need to agree on checked + * mode versus production mode or debug/release/product. + * + * \return Returns an error handle if a compilation error was encountered. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_LoadCompilationTrace(uint8_t* buffer, intptr_t buffer_length); + +/** + * Record runtime feedback for the current isolate, including type feedback + * and usage counters. + * + * \param buffer Returns a pointer to a buffer containing the trace. + * This buffer is scope allocated and is only valid until the next call to + * Dart_ExitScope. + * \param size Returns the size of the buffer. + * \return Returns an valid handle upon success. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_SaveTypeFeedback(uint8_t** buffer, intptr_t* buffer_length); + +/** + * Compile functions using data from Dart_SaveTypeFeedback. The data must from a + * VM with the same version and compiler flags. + * + * \return Returns an error handle if a compilation error was encountered or a + * version mismatch is detected. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_LoadTypeFeedback(uint8_t* buffer, intptr_t buffer_length); + +/* + * ============== + * Precompilation + * ============== + */ + +/** + * Compiles all functions reachable from entry points and marks + * the isolate to disallow future compilation. + * + * Entry points should be specified using `@pragma("vm:entry-point")` + * annotation. + * + * \return An error handle if a compilation error or runtime error running const + * constructors was encountered. + */ +DART_EXPORT Dart_Handle Dart_Precompile(); + +typedef void (*Dart_CreateLoadingUnitCallback)( + void* callback_data, + intptr_t loading_unit_id, + void** write_callback_data, + void** write_debug_callback_data); +typedef void (*Dart_StreamingWriteCallback)(void* callback_data, + const uint8_t* buffer, + intptr_t size); +typedef void (*Dart_StreamingCloseCallback)(void* callback_data); + +DART_EXPORT Dart_Handle Dart_LoadingUnitLibraryUris(intptr_t loading_unit_id); + +// On Darwin systems, 'dlsym' adds an '_' to the beginning of the symbol name. +// Use the '...CSymbol' definitions for resolving through 'dlsym'. The actual +// symbol names in the objects are given by the '...AsmSymbol' definitions. +#if defined(__APPLE__) +#define kSnapshotBuildIdCSymbol "kDartSnapshotBuildId" +#define kVmSnapshotDataCSymbol "kDartVmSnapshotData" +#define kVmSnapshotInstructionsCSymbol "kDartVmSnapshotInstructions" +#define kVmSnapshotBssCSymbol "kDartVmSnapshotBss" +#define kIsolateSnapshotDataCSymbol "kDartIsolateSnapshotData" +#define kIsolateSnapshotInstructionsCSymbol "kDartIsolateSnapshotInstructions" +#define kIsolateSnapshotBssCSymbol "kDartIsolateSnapshotBss" +#else +#define kSnapshotBuildIdCSymbol "_kDartSnapshotBuildId" +#define kVmSnapshotDataCSymbol "_kDartVmSnapshotData" +#define kVmSnapshotInstructionsCSymbol "_kDartVmSnapshotInstructions" +#define kVmSnapshotBssCSymbol "_kDartVmSnapshotBss" +#define kIsolateSnapshotDataCSymbol "_kDartIsolateSnapshotData" +#define kIsolateSnapshotInstructionsCSymbol "_kDartIsolateSnapshotInstructions" +#define kIsolateSnapshotBssCSymbol "_kDartIsolateSnapshotBss" +#endif + +#define kSnapshotBuildIdAsmSymbol "_kDartSnapshotBuildId" +#define kVmSnapshotDataAsmSymbol "_kDartVmSnapshotData" +#define kVmSnapshotInstructionsAsmSymbol "_kDartVmSnapshotInstructions" +#define kVmSnapshotBssAsmSymbol "_kDartVmSnapshotBss" +#define kIsolateSnapshotDataAsmSymbol "_kDartIsolateSnapshotData" +#define kIsolateSnapshotInstructionsAsmSymbol \ + "_kDartIsolateSnapshotInstructions" +#define kIsolateSnapshotBssAsmSymbol "_kDartIsolateSnapshotBss" + +/** + * Creates a precompiled snapshot. + * - A root library must have been loaded. + * - Dart_Precompile must have been called. + * + * Outputs an assembly file defining the symbols listed in the definitions + * above. + * + * The assembly should be compiled as a static or shared library and linked or + * loaded by the embedder. Running this snapshot requires a VM compiled with + * DART_PRECOMPILED_SNAPSHOT. The kDartVmSnapshotData and + * kDartVmSnapshotInstructions should be passed to Dart_Initialize. The + * kDartIsolateSnapshotData and kDartIsolateSnapshotInstructions should be + * passed to Dart_CreateIsolateGroup. + * + * The callback will be invoked one or more times to provide the assembly code. + * + * If stripped is true, then the assembly code will not include DWARF + * debugging sections. + * + * If debug_callback_data is provided, debug_callback_data will be used with + * the callback to provide separate debugging information. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppAOTSnapshotAsAssembly(Dart_StreamingWriteCallback callback, + void* callback_data, + bool stripped, + void* debug_callback_data); +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppAOTSnapshotAsAssemblies( + Dart_CreateLoadingUnitCallback next_callback, + void* next_callback_data, + bool stripped, + Dart_StreamingWriteCallback write_callback, + Dart_StreamingCloseCallback close_callback); + +/** + * Creates a precompiled snapshot. + * - A root library must have been loaded. + * - Dart_Precompile must have been called. + * + * Outputs an ELF shared library defining the symbols + * - _kDartVmSnapshotData + * - _kDartVmSnapshotInstructions + * - _kDartIsolateSnapshotData + * - _kDartIsolateSnapshotInstructions + * + * The shared library should be dynamically loaded by the embedder. + * Running this snapshot requires a VM compiled with DART_PRECOMPILED_SNAPSHOT. + * The kDartVmSnapshotData and kDartVmSnapshotInstructions should be passed to + * Dart_Initialize. The kDartIsolateSnapshotData and + * kDartIsolateSnapshotInstructions should be passed to Dart_CreateIsolate. + * + * The callback will be invoked one or more times to provide the binary output. + * + * If stripped is true, then the binary output will not include DWARF + * debugging sections. + * + * If debug_callback_data is provided, debug_callback_data will be used with + * the callback to provide separate debugging information. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppAOTSnapshotAsElf(Dart_StreamingWriteCallback callback, + void* callback_data, + bool stripped, + void* debug_callback_data); +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppAOTSnapshotAsElfs(Dart_CreateLoadingUnitCallback next_callback, + void* next_callback_data, + bool stripped, + Dart_StreamingWriteCallback write_callback, + Dart_StreamingCloseCallback close_callback); + +/** + * Like Dart_CreateAppAOTSnapshotAsAssembly, but only includes + * kDartVmSnapshotData and kDartVmSnapshotInstructions. It also does + * not strip DWARF information from the generated assembly or allow for + * separate debug information. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateVMAOTSnapshotAsAssembly(Dart_StreamingWriteCallback callback, + void* callback_data); + +/** + * Sorts the class-ids in depth first traversal order of the inheritance + * tree. This is a costly operation, but it can make method dispatch + * more efficient and is done before writing snapshots. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_SortClasses(); + +/** + * Creates a snapshot that caches compiled code and type feedback for faster + * startup and quicker warmup in a subsequent process. + * + * Outputs a snapshot in two pieces. The pieces should be passed to + * Dart_CreateIsolateGroup in a VM using the same VM snapshot pieces used in the + * current VM. The instructions piece must be loaded with read and execute + * permissions; the data piece may be loaded as read-only. + * + * - Requires the VM to have not been started with --precompilation. + * - Not supported when targeting IA32. + * - The VM writing the snapshot and the VM reading the snapshot must be the + * same version, must be built in the same DEBUG/RELEASE/PRODUCT mode, must + * be targeting the same architecture, and must both be in checked mode or + * both in unchecked mode. + * + * The buffers are scope allocated and are only valid until the next call to + * Dart_ExitScope. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppJITSnapshotAsBlobs(uint8_t** isolate_snapshot_data_buffer, + intptr_t* isolate_snapshot_data_size, + uint8_t** isolate_snapshot_instructions_buffer, + intptr_t* isolate_snapshot_instructions_size); + +/** + * Like Dart_CreateAppJITSnapshotAsBlobs, but also creates a new VM snapshot. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateCoreJITSnapshotAsBlobs( + uint8_t** vm_snapshot_data_buffer, + intptr_t* vm_snapshot_data_size, + uint8_t** vm_snapshot_instructions_buffer, + intptr_t* vm_snapshot_instructions_size, + uint8_t** isolate_snapshot_data_buffer, + intptr_t* isolate_snapshot_data_size, + uint8_t** isolate_snapshot_instructions_buffer, + intptr_t* isolate_snapshot_instructions_size); + +/** + * Get obfuscation map for precompiled code. + * + * Obfuscation map is encoded as a JSON array of pairs (original name, + * obfuscated name). + * + * \return Returns an error handler if the VM was built in a mode that does not + * support obfuscation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_GetObfuscationMap(uint8_t** buffer, intptr_t* buffer_length); + +/** + * Returns whether the VM only supports running from precompiled snapshots and + * not from any other kind of snapshot or from source (that is, the VM was + * compiled with DART_PRECOMPILED_RUNTIME). + */ +DART_EXPORT bool Dart_IsPrecompiledRuntime(); + +/** + * Print a native stack trace. Used for crash handling. + * + * If context is NULL, prints the current stack trace. Otherwise, context + * should be a CONTEXT* (Windows) or ucontext_t* (POSIX) from a signal handler + * running on the current thread. + */ +DART_EXPORT void Dart_DumpNativeStackTrace(void* context); + +/** + * Indicate that the process is about to abort, and the Dart VM should not + * attempt to cleanup resources. + */ +DART_EXPORT void Dart_PrepareToAbort(); + +#endif /* INCLUDE_DART_API_H_ */ /* NOLINT */ diff --git a/src/bindings/ejdb2_dart/lib/dart_native_api.h b/src/bindings/ejdb2_dart/lib/dart_native_api.h new file mode 100644 index 0000000..a40c522 --- /dev/null +++ b/src/bindings/ejdb2_dart/lib/dart_native_api.h @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#ifndef RUNTIME_INCLUDE_DART_NATIVE_API_H_ +#define RUNTIME_INCLUDE_DART_NATIVE_API_H_ + +#include "dart_api.h" /* NOLINT */ + +/* + * ========================================== + * Message sending/receiving from native code + * ========================================== + */ + +/** + * A Dart_CObject is used for representing Dart objects as native C + * data outside the Dart heap. These objects are totally detached from + * the Dart heap. Only a subset of the Dart objects have a + * representation as a Dart_CObject. + * + * The string encoding in the 'value.as_string' is UTF-8. + * + * All the different types from dart:typed_data are exposed as type + * kTypedData. The specific type from dart:typed_data is in the type + * field of the as_typed_data structure. The length in the + * as_typed_data structure is always in bytes. + * + * The data for kTypedData is copied on message send and ownership remains with + * the caller. The ownership of data for kExternalTyped is passed to the VM on + * message send and returned when the VM invokes the + * Dart_HandleFinalizer callback; a non-NULL callback must be provided. + */ +typedef enum { + Dart_CObject_kNull = 0, + Dart_CObject_kBool, + Dart_CObject_kInt32, + Dart_CObject_kInt64, + Dart_CObject_kDouble, + Dart_CObject_kString, + Dart_CObject_kArray, + Dart_CObject_kTypedData, + Dart_CObject_kExternalTypedData, + Dart_CObject_kSendPort, + Dart_CObject_kCapability, + Dart_CObject_kUnsupported, + Dart_CObject_kNumberOfTypes +} Dart_CObject_Type; + +typedef struct _Dart_CObject { + Dart_CObject_Type type; + union { + bool as_bool; + int32_t as_int32; + int64_t as_int64; + double as_double; + char* as_string; + struct { + Dart_Port id; + Dart_Port origin_id; + } as_send_port; + struct { + int64_t id; + } as_capability; + struct { + intptr_t length; + struct _Dart_CObject** values; + } as_array; + struct { + Dart_TypedData_Type type; + intptr_t length; + uint8_t* values; + } as_typed_data; + struct { + Dart_TypedData_Type type; + intptr_t length; + uint8_t* data; + void* peer; + Dart_HandleFinalizer callback; + } as_external_typed_data; + } value; +} Dart_CObject; +// This struct is versioned by DART_API_DL_MAJOR_VERSION, bump the version when +// changing this struct. + +/** + * Posts a message on some port. The message will contain the Dart_CObject + * object graph rooted in 'message'. + * + * While the message is being sent the state of the graph of Dart_CObject + * structures rooted in 'message' should not be accessed, as the message + * generation will make temporary modifications to the data. When the message + * has been sent the graph will be fully restored. + * + * If true is returned, the message was enqueued, and finalizers for external + * typed data will eventually run, even if the receiving isolate shuts down + * before processing the message. If false is returned, the message was not + * enqueued and ownership of external typed data in the message remains with the + * caller. + * + * This function may be called on any thread when the VM is running (that is, + * after Dart_Initialize has returned and before Dart_Cleanup has been called). + * + * \param port_id The destination port. + * \param message The message to send. + * + * \return True if the message was posted. + */ +DART_EXPORT bool Dart_PostCObject(Dart_Port port_id, Dart_CObject* message); + +/** + * Posts a message on some port. The message will contain the integer 'message'. + * + * \param port_id The destination port. + * \param message The message to send. + * + * \return True if the message was posted. + */ +DART_EXPORT bool Dart_PostInteger(Dart_Port port_id, int64_t message); + +/** + * A native message handler. + * + * This handler is associated with a native port by calling + * Dart_NewNativePort. + * + * The message received is decoded into the message structure. The + * lifetime of the message data is controlled by the caller. All the + * data references from the message are allocated by the caller and + * will be reclaimed when returning to it. + */ +typedef void (*Dart_NativeMessageHandler)(Dart_Port dest_port_id, + Dart_CObject* message); + +/** + * Creates a new native port. When messages are received on this + * native port, then they will be dispatched to the provided native + * message handler. + * + * \param name The name of this port in debugging messages. + * \param handler The C handler to run when messages arrive on the port. + * \param handle_concurrently Is it okay to process requests on this + * native port concurrently? + * + * \return If successful, returns the port id for the native port. In + * case of error, returns ILLEGAL_PORT. + */ +DART_EXPORT Dart_Port Dart_NewNativePort(const char* name, + Dart_NativeMessageHandler handler, + bool handle_concurrently); +/* TODO(turnidge): Currently handle_concurrently is ignored. */ + +/** + * Closes the native port with the given id. + * + * The port must have been allocated by a call to Dart_NewNativePort. + * + * \param native_port_id The id of the native port to close. + * + * \return Returns true if the port was closed successfully. + */ +DART_EXPORT bool Dart_CloseNativePort(Dart_Port native_port_id); + +/* + * ================== + * Verification Tools + * ================== + */ + +/** + * Forces all loaded classes and functions to be compiled eagerly in + * the current isolate.. + * + * TODO(turnidge): Document. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_CompileAll(); + +/** + * Finalizes all classes. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_FinalizeAllClasses(); + +/* This function is intentionally undocumented. + * + * It should not be used outside internal tests. + */ +DART_EXPORT void* Dart_ExecuteInternalCommand(const char* command, void* arg); + +#endif /* INCLUDE_DART_NATIVE_API_H_ */ /* NOLINT */ diff --git a/src/bindings/ejdb2_dart/lib/ejdb2_dart.c b/src/bindings/ejdb2_dart/lib/ejdb2_dart.c new file mode 100644 index 0000000..8834abb --- /dev/null +++ b/src/bindings/ejdb2_dart/lib/ejdb2_dart.c @@ -0,0 +1,1506 @@ +#define DART_SHARED_LIB +#include "dart_api.h" +#include "dart_native_api.h" +#include +#include +#include +#include +#include +#include + +typedef void (*WrapperFunction)(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port); + +typedef enum { + _EJD_ERROR_START = (IW_ERROR_START + 15000UL + 4000), + EJD_ERROR_CREATE_PORT, /**< Failed to create a Dart port (EJD_ERROR_CREATE_PORT) */ + EJD_ERROR_POST_PORT, /**< Failed to post message to Dart port (EJD_ERROR_POST_PORT) */ + EJD_ERROR_INVALID_NATIVE_CALL_ARGS, /**< Invalid native function call args (EJD_ERROR_INVALID_NATIVE_CALL_ARGS) */ + EJD_ERROR_INVALID_STATE, /**< Invalid native extension state (EJD_ERROR_INVALID_STATE) */ + _EJD_ERROR_END, +} jbr_ecode_t; + +struct NativeFunctionLookup { + const char *name; + Dart_NativeFunction fn; +}; + +struct WrapperFunctionLookup { + const char *name; + WrapperFunction fn; +}; + +typedef struct EJDB2Handle { + EJDB db; + char *path; + int64_t refs; + struct EJDB2Handle *next; + struct EJDB2Handle *prev; +} EJDB2Handle; + +typedef struct EJDB2Context { + Dart_Port port; + EJDB2Handle *dbh; + Dart_WeakPersistentHandle wph; +} EJDB2Context; + +typedef struct EJDB2JQLContext { + JQL q; + Dart_WeakPersistentHandle wph; +} EJDB2JQLContext; + +static pthread_mutex_t k_shared_scope_mtx = PTHREAD_MUTEX_INITIALIZER; +static EJDB2Handle *k_head_handle; + +static Dart_NativeFunction ejd_resolve_name(Dart_Handle name, int argc, bool *auto_setup_scope); + +IW_INLINE Dart_Handle ejd_error_check_propagate(Dart_Handle handle); +IW_INLINE Dart_Handle ejd_error_rc_create(iwrc rc); +IW_INLINE void ejd_error_rc_throw(iwrc rc); + +static void ejd_explain_rc(Dart_NativeArguments args); +static void ejd_exec(Dart_NativeArguments args); +static void ejd_exec_check(Dart_NativeArguments args); +static void ejd_jql_set(Dart_NativeArguments args); +static void ejd_jql_get_limit(Dart_NativeArguments args); + +static void ejd_port_handler(Dart_Port receive_port, Dart_CObject *msg); +static void ejd_ctx_finalizer(void *isolate_callback_data, void *peer); + +static void ejd_port(Dart_NativeArguments arguments); +static void ejd_set_handle(Dart_NativeArguments args); +static void ejd_get_handle(Dart_NativeArguments args); +static void ejd_create_query(Dart_NativeArguments args); + +static void ejd_open_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port); +static void ejd_close_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port); +static void ejd_get_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port); +static void ejd_put_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port); +static void ejd_del_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port); +static void ejd_patch_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port); +static void ejd_idx_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port); +static void ejd_rmi_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port); +static void ejd_rmc_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port); +static void ejd_info_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port); +static void ejd_rename_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port); +static void ejd_bkp_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port); + +static struct NativeFunctionLookup k_scoped_functions[] = { + { "port", ejd_port }, + { "exec", ejd_exec }, + { "check_exec", ejd_exec_check }, + { "jql_set", ejd_jql_set }, + { "jql_get_limit", ejd_jql_get_limit }, + { "create_query", ejd_create_query }, + { "set_handle", ejd_set_handle }, + { "get_handle", ejd_get_handle }, + { "explain_rc", ejd_explain_rc }, + { 0, 0 } +}; + +static struct WrapperFunctionLookup k_wrapped_functions[] = { + { "get", ejd_get_wrapped }, + { "put", ejd_put_wrapped }, + { "del", ejd_del_wrapped }, + { "rename", ejd_rename_wrapped }, + { "patch", ejd_patch_wrapped }, + { "idx", ejd_idx_wrapped }, + { "rmi", ejd_rmi_wrapped }, + { "rmc", ejd_rmc_wrapped }, + { "info", ejd_info_wrapped }, + { "open", ejd_open_wrapped }, + { "close", ejd_close_wrapped }, + { "bkp", ejd_bkp_wrapped }, + { 0, 0 } +}; + +#define EJTH(h_) ejd_error_check_propagate(h_) + +#define EJGO(h_, rh_, label_) \ + if (Dart_IsError(h_)) { \ + rh_ = (h_); \ + goto label_; \ + } + +#define EJLIB() EJTH(Dart_LookupLibrary(Dart_NewStringFromCString("package:ejdb2_dart/ejdb2_dart.dart"))) + +#define EJPORT_RC(co_, rc_) \ + if (rc_) { \ + (co_)->type = Dart_CObject_kInt64; \ + (co_)->value.as_int64 = (rc_); \ + } + +IW_INLINE char *cobject_str(Dart_CObject *co, bool nulls, iwrc *rcp) { + *rcp = 0; + if (co) { + if (co->type == Dart_CObject_kString) { + return co->value.as_string; + } else if (nulls && (co->type == Dart_CObject_kNull)) { + return 0; + } + } + *rcp = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + return 0; +} + +IW_INLINE int64_t cobject_int(Dart_CObject *co, bool nulls, iwrc *rcp) { + *rcp = 0; + if (co) { + if (co->type == Dart_CObject_kInt32) { + return co->value.as_int32; + } else if (co->type == Dart_CObject_kInt64) { + return co->value.as_int64; + } else if (nulls && (co->type == Dart_CObject_kNull)) { + return 0; + } + } + *rcp = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + return 0; +} + +IW_INLINE bool cobject_bool(Dart_CObject *co, bool nulls, iwrc *rcp) { + *rcp = 0; + if (co) { + if (co->type == Dart_CObject_kBool) { + return co->value.as_bool; + } else if (nulls && (co->type == Dart_CObject_kNull)) { + return false; + } + } + *rcp = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + return false; +} + +IW_INLINE double cobject_double(Dart_CObject *co, bool nulls, iwrc *rcp) { + *rcp = 0; + if (co) { + if (co->type == Dart_CObject_kDouble) { + return co->value.as_double; + } else if (nulls && (co->type == Dart_CObject_kNull)) { + return 0; + } + } + *rcp = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + return 0; +} + +IW_INLINE Dart_Handle ejd_error_check_propagate(Dart_Handle handle) { + if (Dart_IsError(handle)) { + Dart_PropagateError(handle); + } + return handle; +} + +static Dart_Handle ejd_error_object_handle(iwrc rc, const char *msg) { + Dart_Handle hmsg = Dart_Null(); + if (msg) { + hmsg = EJTH(Dart_NewStringFromCString(msg)); + } else if (rc) { + const char *explained = iwlog_ecode_explained(rc); + if (explained) { + hmsg = EJTH(Dart_NewStringFromCString(explained)); + } + } + Dart_Handle hrc = EJTH(Dart_NewIntegerFromUint64(rc)); + Dart_Handle hclass = EJTH(Dart_GetClass(EJLIB(), Dart_NewStringFromCString("EJDB2Error"))); + Dart_Handle args[] = { hrc, hmsg }; + return Dart_New(hclass, Dart_Null(), 2, args); +} + +IW_INLINE Dart_Handle ejd_error_rc_create(iwrc rc) { + const char *msg = iwlog_ecode_explained(rc); + return Dart_NewUnhandledExceptionError(ejd_error_object_handle(rc, msg)); +} + +IW_INLINE Dart_Handle ejd_error_rc_create2(iwrc rc, const char *msg) { + return Dart_NewUnhandledExceptionError(ejd_error_object_handle(rc, msg)); +} + +IW_INLINE void ejd_error_rc_throw(iwrc rc) { + Dart_PropagateError(ejd_error_rc_create(rc)); +} + +static void ejd_port(Dart_NativeArguments args) { + EJDB2Context *ctx; + intptr_t ptr = 0; + + Dart_EnterScope(); + Dart_Handle self = EJTH(Dart_GetNativeArgument(args, 0)); + EJTH(Dart_GetNativeInstanceField(self, 0, &ptr)); + + if (!ptr) { + ctx = malloc(sizeof(*ctx)); + if (!ctx) { + Dart_SetReturnValue(args, ejd_error_rc_create(IW_ERROR_ALLOC)); + goto finish; + } + ctx->dbh = 0; + ctx->port = Dart_NewNativePort("ejd_port_handler", ejd_port_handler, true); + if (ctx->port == ILLEGAL_PORT) { + Dart_SetReturnValue(args, ejd_error_rc_create(EJD_ERROR_CREATE_PORT)); + goto finish; + } + ctx->wph = Dart_NewWeakPersistentHandle(self, ctx, sizeof(*ctx), ejd_ctx_finalizer); + if (!ctx->wph) { + Dart_SetReturnValue(args, ejd_error_rc_create(EJD_ERROR_INVALID_STATE)); + goto finish; + } + Dart_SetNativeInstanceField(self, 0, (intptr_t) ctx); + } else { + ctx = (void*) ptr; + } + Dart_SetReturnValue(args, EJTH(Dart_NewSendPort(ctx->port))); + +finish: + Dart_ExitScope(); +} + +static void ejd_get_handle(Dart_NativeArguments args) { + intptr_t ptr = 0; + Dart_EnterScope(); + Dart_Handle self = EJTH(Dart_GetNativeArgument(args, 0)); + EJTH(Dart_GetNativeInstanceField(self, 0, &ptr)); + EJDB2Context *ctx = (void*) ptr; + if (ctx && ctx->dbh) { + Dart_SetReturnValue(args, Dart_NewInteger((intptr_t) ctx->dbh)); + } else { + Dart_SetReturnValue(args, Dart_Null()); + } + Dart_ExitScope(); +} + +static void ejd_set_handle(Dart_NativeArguments args) { + intptr_t ptr; + Dart_EnterScope(); + Dart_SetReturnValue(args, Dart_Null()); + Dart_Handle self = EJTH(Dart_GetNativeArgument(args, 0)); + EJTH(Dart_GetNativeInstanceField(self, 0, &ptr)); + EJDB2Context *ctx = (void*) ptr; + if (!ctx) { + Dart_SetReturnValue(args, ejd_error_rc_create(EJD_ERROR_INVALID_STATE)); + goto finish; + } + Dart_Handle dh = Dart_GetNativeArgument(args, 1); + if (Dart_IsInteger(dh)) { + int64_t llv; + EJTH(Dart_GetNativeIntegerArgument(args, 1, &llv)); + ctx->dbh = (void*) llv; + } else if (Dart_IsNull(dh)) { + ctx->dbh = 0; + } + +finish: + Dart_ExitScope(); +} + +static void ejd_jql_finalizer(void *isolate_callback_data, void *peer) { + EJDB2JQLContext *ctx = peer; + if (ctx) { + if (ctx->q) { + jql_destroy(&ctx->q); + } + if (ctx->wph && Dart_CurrentIsolateGroup()) { + Dart_DeleteWeakPersistentHandle(ctx->wph); + } + free(ctx); + } +} + +static void ejd_create_query(Dart_NativeArguments args) { + Dart_EnterScope(); + + iwrc rc = 0; + JQL q = 0; + intptr_t ptr = 0; + const char *query = 0; + const char *collection = 0; + EJDB2JQLContext *qctx = 0; + + Dart_Handle ret = Dart_Null(); + Dart_Handle hlib = EJLIB(); + Dart_Handle hself = EJTH(Dart_GetNativeArgument(args, 0)); + Dart_Handle hquery = EJTH(Dart_GetNativeArgument(args, 1)); + Dart_Handle hcoll = EJTH(Dart_GetNativeArgument(args, 2)); + + EJTH(Dart_StringToCString(hquery, &query)); + if (Dart_IsString(hcoll)) { + EJTH(Dart_StringToCString(hcoll, &collection)); + } + EJTH(Dart_GetNativeInstanceField(hself, 0, &ptr)); + + EJDB2Context *ctx = (void*) ptr; + if (!ctx || !ctx->dbh || !ctx->dbh->db) { + rc = EJD_ERROR_INVALID_STATE; + goto finish; + } + rc = jql_create2(&q, collection, query, JQL_KEEP_QUERY_ON_PARSE_ERROR | JQL_SILENT_ON_PARSE_ERROR); + RCGO(rc, finish); + + if (!collection) { + collection = jql_collection(q); + } + hcoll = Dart_NewStringFromCString(collection); + EJGO(hcoll, ret, finish); + + Dart_Handle hclass = Dart_GetClass(hlib, Dart_NewStringFromCString("JQL")); + EJGO(hclass, ret, finish); + + Dart_Handle jqargs[] = { hself, hquery, hcoll }; + Dart_Handle jqinst = Dart_New(hclass, Dart_NewStringFromCString("_"), 3, jqargs); + EJGO(jqinst, ret, finish); + + ret = Dart_SetNativeInstanceField(jqinst, 0, (intptr_t) q); + EJGO(ret, ret, finish); + + qctx = malloc(sizeof(*qctx)); + if (!qctx) { + Dart_SetReturnValue(args, ejd_error_rc_create(IW_ERROR_ALLOC)); + goto finish; + } + + qctx->q = q; + qctx->wph = Dart_NewWeakPersistentHandle(jqinst, qctx, jql_estimate_allocated_size(q) + sizeof(*qctx), + ejd_jql_finalizer); + if (!qctx->wph) { + rc = EJD_ERROR_INVALID_STATE; + goto finish; + } + + ret = jqinst; + +finish: + if (rc || Dart_IsError(ret)) { + if (rc) { + if (q && (rc == JQL_ERROR_QUERY_PARSE)) { + ret = ejd_error_rc_create2(rc, jql_error(q)); + } else { + ret = ejd_error_rc_create(rc); + } + } + if (q) { + jql_destroy(&q); + } + free(qctx); + } + Dart_SetReturnValue(args, ret); + Dart_ExitScope(); +} + +// Query execution context +// Contains a state to manage resultset backpressure +typedef struct QCTX { + bool aggregate_count; + bool explain; + bool paused; + int pending_count; + Dart_Port reply_port; + JQL q; + EJDB2Context *dctx; + int64_t limit; + pthread_mutex_t mtx; + pthread_cond_t cond; +} *QCTX; + +static iwrc ejd_exec_pause_guard(QCTX qctx) { + iwrc rc = 0; + int rci = pthread_mutex_lock(&qctx->mtx); + if (rci) { + return iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci); + } + while (qctx->paused) { + rci = pthread_cond_wait(&qctx->cond, &qctx->mtx); + if (rci) { + rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci); + break; + } + } + qctx->pending_count++; + pthread_mutex_unlock(&qctx->mtx); + return rc; +} + +static iwrc ejd_exec_visitor(struct _EJDB_EXEC *ux, EJDB_DOC doc, int64_t *step) { + iwrc rc = 0; + QCTX qctx = ux->opaque; + + rc = ejd_exec_pause_guard(qctx); + RCRET(rc); + + IWXSTR *xstr = iwxstr_new(); + if (!xstr) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + if (doc->node) { + rc = jbn_as_json(doc->node, jbl_xstr_json_printer, xstr, 0); + } else { + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + } + RCGO(rc, finish); + + Dart_CObject result, rv1, rv2, rv3; + Dart_CObject *rv[] = { &rv1, &rv2, &rv3 }; + result.type = Dart_CObject_kArray; + result.value.as_array.length = sizeof(rv) / sizeof(rv[0]); + result.value.as_array.values = rv; + rv1.type = Dart_CObject_kInt64; + rv1.value.as_int64 = doc->id; + rv2.type = Dart_CObject_kString; + rv2.value.as_string = iwxstr_ptr(xstr); + if (ux->log) { // Add explain log to the first record + rv3.type = Dart_CObject_kString; + rv3.value.as_string = iwxstr_ptr(ux->log); + ux->log = 0; + } else { + rv3.type = Dart_CObject_kNull; + } + if (!Dart_PostCObject(qctx->reply_port, &result)) { + *step = 0; // End of cursor loop + } + +finish: + iwxstr_destroy(xstr); + return rc; +} + +static void ejd_exec_port_handler(Dart_Port receive_port, Dart_CObject *msg) { + iwrc rc = 0; + Dart_CObject result = { .type = Dart_CObject_kNull }; + if ((msg->type != Dart_CObject_kInt64) || !msg->value.as_int64) { + iwlog_error2("Invalid message recieved"); + return; + } + QCTX qctx = (void*) msg->value.as_int64; + IWXSTR *exlog = 0; + EJDB_EXEC ux = { 0 }; + EJDB2Context *dctx = qctx->dctx; + + if (!qctx->q || !dctx || !dctx->dbh || !dctx->dbh->db) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + if (qctx->explain) { + exlog = iwxstr_new(); + if (!exlog) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + } + + qctx->aggregate_count = jql_has_aggregate_count(qctx->q); + + ux.q = qctx->q; + ux.db = dctx->dbh->db; + ux.visitor = qctx->aggregate_count ? 0 : ejd_exec_visitor; + ux.opaque = qctx; + ux.log = exlog; + ux.limit = qctx->limit; + + rc = ejdb_exec(&ux); + RCGO(rc, finish); + + if (qctx->aggregate_count) { + Dart_CObject result, rv1, rv2, rv3; + Dart_CObject *rv[] = { &rv1, &rv2, &rv3 }; + result.type = Dart_CObject_kArray; + result.value.as_array.length = (sizeof(rv) / sizeof(rv[0])); + result.value.as_array.values = rv; + rv1.type = Dart_CObject_kInt64; + rv1.value.as_int64 = ux.cnt; + rv2.type = Dart_CObject_kNull; + if (exlog) { + rv3.type = Dart_CObject_kString; + rv3.value.as_string = iwxstr_ptr(exlog); + } else { + rv3.type = Dart_CObject_kNull; + } + Dart_PostCObject(qctx->reply_port, &result); + } else if (exlog && (ux.cnt == 0)) { + result.type = Dart_CObject_kString; + result.value.as_string = iwxstr_ptr(exlog); + } + +finish: + if (rc) { + iwlog_ecode_error3(rc); + EJPORT_RC(&result, rc); + } + if (qctx->reply_port != ILLEGAL_PORT) { + Dart_PostCObject(qctx->reply_port, &result); // Last NULL or error(int) + } + if (exlog) { + iwxstr_destroy(exlog); + } + Dart_CloseNativePort(receive_port); +} + +static void ejd_exec(Dart_NativeArguments args) { + Dart_EnterScope(); + + iwrc rc = 0; + intptr_t qptr, ptr = 0; + bool explain = false; + int64_t limit = 0; + QCTX qctx = 0; + + Dart_Port reply_port = ILLEGAL_PORT; + Dart_Port exec_port = ILLEGAL_PORT; + Dart_Handle ret = Dart_Null(); + + Dart_Handle hself = EJTH(Dart_GetNativeArgument(args, 0)); + Dart_Handle hdb = EJTH(Dart_GetField(hself, Dart_NewStringFromCString("db"))); + Dart_Handle hport = EJTH(Dart_GetNativeArgument(args, 1)); + + EJTH(Dart_GetNativeBooleanArgument(args, 2, &explain)); + EJTH(Dart_GetNativeIntegerArgument(args, 3, &limit)); + EJTH(Dart_SendPortGetId(hport, &reply_port)); + + EJTH(Dart_GetNativeInstanceField(hdb, 0, &ptr)); + EJDB2Context *dctx = (void*) ptr; + if (!dctx || !dctx->dbh || !dctx->dbh->db) { + rc = EJD_ERROR_INVALID_STATE; + goto finish; + } + + // JQL pointer + EJTH(Dart_GetNativeInstanceField(hself, 0, &qptr)); + if (!qptr) { + rc = EJD_ERROR_INVALID_STATE; + goto finish; + } + + // JQL exec port + exec_port = Dart_NewNativePort("ejd_exec_port_handler", ejd_exec_port_handler, false); + if (exec_port == ILLEGAL_PORT) { + rc = EJD_ERROR_CREATE_PORT; + goto finish; + } + + qctx = calloc(1, sizeof(*qctx)); + if (!qctx) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + + pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; + pthread_cond_t cond = PTHREAD_COND_INITIALIZER; + memcpy(&qctx->mtx, &mtx, sizeof(mtx)); + memcpy(&qctx->cond, &cond, sizeof(cond)); + + qctx->reply_port = reply_port; + qctx->q = (void*) qptr; + qctx->dctx = dctx; + qctx->explain = explain; + qctx->limit = limit; + + // Now post a message to the query executor + Dart_CObject msg; + msg.type = Dart_CObject_kInt64; + msg.value.as_int64 = (int64_t) qctx; + + if (!Dart_PostCObject(exec_port, &msg)) { + rc = EJD_ERROR_POST_PORT; + goto finish; + } + +finish: + if (rc || Dart_IsError(ret)) { + if (qctx) { + free(qctx); + qctx = 0; + } + if (exec_port != ILLEGAL_PORT) { + Dart_CloseNativePort(exec_port); + } + if (rc) { + ret = ejd_error_rc_create(rc); + } + } + if (qctx) { + ret = Dart_NewInteger((int64_t) (void*) qctx); + } + Dart_SetReturnValue(args, ret); + Dart_ExitScope(); +} + +static void ejd_exec_check(Dart_NativeArguments args) { + iwrc rc = 0; + Dart_EnterScope(); + Dart_Handle ret = Dart_Null(); + + if (Dart_GetNativeArgumentCount(args) < 3) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + bool terminate = false; + int64_t hptr = 0; + + EJTH(Dart_GetNativeIntegerArgument(args, 1, &hptr)); + EJTH(Dart_GetNativeBooleanArgument(args, 2, &terminate)); + + if (hptr < 1) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + QCTX qctx = (void*) hptr; + + if (terminate) { + free(qctx); + goto finish; + } + + int rci = pthread_mutex_lock(&qctx->mtx); + if (rci) { + rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci); + goto finish; + } + qctx->pending_count--; + if (qctx->paused) { + if (qctx->pending_count < 32) { + qctx->paused = false; + pthread_cond_broadcast(&qctx->cond); + } + } else if (qctx->pending_count > 64) { + qctx->paused = true; + pthread_cond_broadcast(&qctx->cond); + } + pthread_mutex_unlock(&qctx->mtx); + +finish: + if (rc) { + ret = ejd_error_rc_create(rc); + } + Dart_SetReturnValue(args, ret); + Dart_ExitScope(); +} + +static void ejd_free_str(void *ptr, void *op) { + free(ptr); +} + +static void ejd_free_json_node(void *ptr, void *op) { + IWPOOL *pool = op; + if (pool) { + iwpool_destroy(pool); + } +} + +static void ejd_jql_get_limit(Dart_NativeArguments args) { + Dart_EnterScope(); + Dart_Handle ret = Dart_Null(); + iwrc rc; + int64_t limit; + intptr_t ptr = 0; + Dart_Handle hself = EJTH(Dart_GetNativeArgument(args, 0)); + EJTH(Dart_GetNativeInstanceField(hself, 0, &ptr)); + JQL q = (void*) ptr; + if (!q) { + rc = EJD_ERROR_INVALID_STATE; + goto finish; + } + rc = jql_get_limit(q, &limit); + RCGO(rc, finish); + ret = Dart_NewInteger(limit); + +finish: + if (rc || Dart_IsError(ret)) { + if (rc) { + ret = ejd_error_rc_create(rc); + } + } + Dart_SetReturnValue(args, ret); + Dart_ExitScope(); +} + +static void ejd_jql_set(Dart_NativeArguments args) { + // void set(dynamic place, dynamic value) native 'ejd_jql_set'; + Dart_EnterScope(); + Dart_Handle ret = Dart_Null(); + + iwrc rc = 0; + intptr_t ptr = 0; + Dart_Handle hself = EJTH(Dart_GetNativeArgument(args, 0)); + EJTH(Dart_GetNativeInstanceField(hself, 0, &ptr)); + JQL q = (void*) ptr; + if (!q) { + rc = EJD_ERROR_INVALID_STATE; + goto finish; + } + int64_t npl = 0, type = 0; + const char *svalue, *spl = 0; + Dart_Handle hpl = EJTH(Dart_GetNativeArgument(args, 1)); + Dart_Handle hvalue = EJTH(Dart_GetNativeArgument(args, 2)); + Dart_Handle htype = EJTH(Dart_GetNativeArgument(args, 3)); + + if (Dart_IsString(hpl)) { + EJTH(Dart_StringToCString(hpl, &spl)); + } else { + EJTH(Dart_IntegerToInt64(hpl, &npl)); + } + if (Dart_IsInteger(htype)) { + EJTH(Dart_IntegerToInt64(htype, &type)); + } + if (type == 1) { // JSON + EJTH(Dart_StringToCString(hvalue, &svalue)); + JBL_NODE node; + IWPOOL *pool = iwpool_create(64); + if (!pool) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + rc = jbn_from_json(svalue, &node, pool); + if (rc) { + iwpool_destroy(pool); + goto finish; + } + rc = jql_set_json2(q, spl, npl, node, ejd_free_json_node, pool); + if (rc) { + iwpool_destroy(pool); + goto finish; + } + } else if (type == 2) { // Regexp + EJTH(Dart_StringToCString(hvalue, &svalue)); + char *str = strdup(svalue); + if (!str) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + rc = jql_set_regexp2(q, spl, npl, str, ejd_free_str, 0); + if (rc) { + free(str); + goto finish; + } + } else { // All other cases + if (Dart_IsString(hvalue)) { + EJTH(Dart_StringToCString(hvalue, &svalue)); + char *str = strdup(svalue); + if (!str) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + rc = jql_set_str2(q, spl, npl, str, ejd_free_str, 0); + if (rc) { + free(str); + goto finish; + } + } else if (Dart_IsInteger(hvalue)) { + int64_t val; + EJTH(Dart_IntegerToInt64(hvalue, &val)); + rc = jql_set_i64(q, spl, npl, val); + } else if (Dart_IsDouble(hvalue)) { + double val; + EJTH(Dart_DoubleValue(hvalue, &val)); + rc = jql_set_f64(q, spl, npl, val); + } else if (Dart_IsBoolean(hvalue)) { + bool val; + EJTH(Dart_BooleanValue(hvalue, &val)); + rc = jql_set_bool(q, spl, npl, val); + } else if (Dart_IsNull(hvalue)) { + rc = jql_set_null(q, spl, npl); + } + } + +finish: + if (rc || Dart_IsError(ret)) { + if (rc) { + ret = ejd_error_rc_create(rc); + } + } + Dart_SetReturnValue(args, ret); + Dart_ExitScope(); +} + +static void ejd_explain_rc(Dart_NativeArguments args) { + Dart_EnterScope(); + int64_t llv = 0; + EJTH(Dart_GetNativeIntegerArgument(args, 0, &llv)); + const char *msg = iwlog_ecode_explained((iwrc) llv); + if (msg) { + Dart_SetReturnValue(args, EJTH(Dart_NewStringFromCString(msg))); + } else { + Dart_SetReturnValue(args, Dart_Null()); + } + Dart_ExitScope(); +} + +static Dart_NativeFunction ejd_resolve_name( + Dart_Handle name, + int argc, + bool *auto_setup_scope) { + if (!Dart_IsString(name) || !auto_setup_scope) { + return 0; + } + Dart_EnterScope(); + const char *cname; + EJTH(Dart_StringToCString(name, &cname)); + for (int i = 0; k_scoped_functions[i].name; ++i) { + if (strcmp(cname, k_scoped_functions[i].name) == 0) { + *auto_setup_scope = true; + Dart_ExitScope(); + return k_scoped_functions[i].fn; + } + } + Dart_ExitScope(); + return 0; +} + +static void ejd_port_handler(Dart_Port receive_port, Dart_CObject *msg) { + if ( (msg->type != Dart_CObject_kArray) + || (msg->value.as_array.length < 2) + || (msg->value.as_array.values[0]->type != Dart_CObject_kSendPort) + || (msg->value.as_array.values[1]->type != Dart_CObject_kString)) { + iwlog_error2("Invalid message recieved"); + return; + } + Dart_Port reply_port = msg->value.as_array.values[0]->value.as_send_port.id; + const char *fname = msg->value.as_array.values[1]->value.as_string; + for (int i = 0; k_wrapped_functions[i].name; ++i) { + if (strcmp(k_wrapped_functions[i].name, fname) == 0) { + k_wrapped_functions[i].fn(receive_port, msg, reply_port); + break; + } + } +} + +static iwrc ejdb2_isolate_shared_open(const EJDB_OPTS *opts, EJDB2Handle **hptr) { + iwrc rc = 0; + EJDB db = 0; + int rci = pthread_mutex_lock(&k_shared_scope_mtx); + if (rci) { + return iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci); + } + const char *path = opts->kv.path; + EJDB2Handle *h = k_head_handle; + while (h) { + if (!strcmp(h->path, path)) { + break; + } + h = h->next; + } + if (h) { + h->refs++; + *hptr = h; + goto finish; + } + rc = ejdb_open(opts, &db); + RCGO(rc, finish); + + h = calloc(1, sizeof(*h)); + if (!h) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + h->path = strdup(path); + if (!h->path) { + free(h); + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + h->refs = 1; + h->db = db; + if (k_head_handle) { + k_head_handle->prev = h; + h->next = k_head_handle; + } + k_head_handle = h; + *hptr = h; + +finish: + pthread_mutex_unlock(&k_shared_scope_mtx); + if (rc) { + iwlog_ecode_error3(rc); + if (db) { + ejdb_close(&db); + } + } + return rc; +} + +static iwrc ejdb2_isolate_shared_release(EJDB2Handle **hp) { + iwrc rc = 0; + int rci = pthread_mutex_lock(&k_shared_scope_mtx); + if (rci) { + return iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci); + } + EJDB2Handle *h = *hp; + *hp = 0; + if (--h->refs <= 0) { + if (h->db) { + rc = ejdb_close(&h->db); + } + if (h->prev) { + h->prev->next = h->next; + } else { + k_head_handle = h->next; + } + if (h->next) { + h->next->prev = h->prev; + } + free(h->path); + free(h); + } + pthread_mutex_unlock(&k_shared_scope_mtx); + return rc; +} + +static void ejd_open_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port) { + iwrc rc = 0; + Dart_CObject result, rv1; + Dart_CObject *rv[1] = { &rv1 }; + + int c = 2; + EJDB_OPTS opts = { 0 }; + EJDB2Handle *dbh = 0; + + if ((msg->type != Dart_CObject_kArray) || (msg->value.as_array.length != 16 + c)) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + Dart_CObject **varr = msg->value.as_array.values; + + // opts.kv.path // non null + // opts.kv.oflags // non null + // opts.kv.wal.enabled // non null + // opts.kv.wal.check_crc_on_checkpoint + // opts.kv.wal.checkpoint_buffer_sz + // opts.kv.wal.checkpoint_timeout_sec + // opts.kv.wal.savepoint_timeout_sec + // opts.kv.wal.wal_buffer_sz + // opts.document_buffer_sz + // opts.sort_buffer_sz + // opts.http.enabled + // opts.http.access_token + // opts.http.bind + // opts.http.max_body_size + // opts.http.port + // opts.http.read_anon + + opts.kv.path = cobject_str(varr[c++], false, &rc); + RCGO(rc, finish); + opts.kv.oflags = cobject_int(varr[c++], false, &rc); + RCGO(rc, finish); + opts.kv.wal.enabled = cobject_bool(varr[c++], false, &rc); + RCGO(rc, finish); + opts.kv.wal.check_crc_on_checkpoint = cobject_bool(varr[c++], true, &rc); + RCGO(rc, finish); + opts.kv.wal.checkpoint_buffer_sz = cobject_int(varr[c++], true, &rc); + RCGO(rc, finish); + opts.kv.wal.checkpoint_timeout_sec = cobject_int(varr[c++], true, &rc); + RCGO(rc, finish); + opts.kv.wal.savepoint_timeout_sec = cobject_int(varr[c++], true, &rc); + RCGO(rc, finish); + opts.kv.wal.wal_buffer_sz = cobject_int(varr[c++], true, &rc); + RCGO(rc, finish); + opts.document_buffer_sz = cobject_int(varr[c++], true, &rc); + RCGO(rc, finish); + opts.sort_buffer_sz = cobject_int(varr[c++], true, &rc); + RCGO(rc, finish); + opts.http.enabled = cobject_bool(varr[c++], true, &rc); + RCGO(rc, finish); + opts.http.access_token = cobject_str(varr[c++], true, &rc); + RCGO(rc, finish); + opts.http.bind = cobject_str(varr[c++], true, &rc); + RCGO(rc, finish); + opts.http.max_body_size = cobject_int(varr[c++], true, &rc); + RCGO(rc, finish); + opts.http.port = cobject_int(varr[c++], true, &rc); + RCGO(rc, finish); + opts.http.read_anon = cobject_bool(varr[c++], true, &rc); + RCGO(rc, finish); + + opts.kv.file_lock_fail_fast = true; + opts.no_wal = !opts.kv.wal.enabled; + opts.http.blocking = false; + opts.http.access_token_len = opts.http.access_token ? strlen(opts.http.access_token) : 0; + + rc = ejdb2_isolate_shared_open(&opts, &dbh); + RCGO(rc, finish); + + rv1.type = Dart_CObject_kInt64; + rv1.value.as_int64 = (intptr_t) dbh; + result.type = Dart_CObject_kArray; + result.value.as_array.length = sizeof(rv) / sizeof(rv[0]); + result.value.as_array.values = rv; + +finish: + EJPORT_RC(&result, rc); + Dart_PostCObject(reply_port, &result); +} + +static void ejd_close_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port) { + iwrc rc = 0; + Dart_CObject result = { .type = Dart_CObject_kArray }; + + if (msg->value.as_array.length != 3) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + intptr_t ptr = cobject_int(msg->value.as_array.values[2], false, &rc); + RCGO(rc, finish); + EJDB2Handle *dbh = (EJDB2Handle*) ptr; + if (!dbh) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + rc = ejdb2_isolate_shared_release(&dbh); + RCGO(rc, finish); + + if (receive_port != ILLEGAL_PORT) { + Dart_CloseNativePort(receive_port); + } + +finish: + EJPORT_RC(&result, rc); + Dart_PostCObject(reply_port, &result); + return; +} + +static void ejd_patch_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port) { + iwrc rc = 0; + Dart_CObject result = { .type = Dart_CObject_kArray }; + + int c = 2; + if ((msg->type != Dart_CObject_kArray) || (msg->value.as_array.length != 5 + c)) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + intptr_t ptr = cobject_int(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + EJDB2Handle *dbh = (EJDB2Handle*) ptr; + if (!dbh || !dbh->db) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + EJDB db = dbh->db; + const char *coll = cobject_str(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + const char *patch = cobject_str(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + int64_t id = cobject_int(msg->value.as_array.values[c++], true, &rc); + RCGO(rc, finish); + + bool upsert = cobject_bool(msg->value.as_array.values[c++], true, &rc); + RCGO(rc, finish); + + if (upsert) { + rc = ejdb_merge_or_put(db, coll, patch, id); + } else { + rc = ejdb_patch(db, coll, patch, id); + } + +finish: + EJPORT_RC(&result, rc); + Dart_PostCObject(reply_port, &result); +} + +static void ejd_put_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port) { + iwrc rc = 0; + Dart_CObject result, rv1; + Dart_CObject *rv[1] = { &rv1 }; + + JBL jbl = 0; + int c = 2; + if ((msg->type != Dart_CObject_kArray) || (msg->value.as_array.length != 4 + c)) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + intptr_t ptr = cobject_int(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + EJDB2Handle *dbh = (EJDB2Handle*) ptr; + if (!dbh || !dbh->db) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + EJDB db = dbh->db; + const char *coll = cobject_str(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + const char *json = cobject_str(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + int64_t id = cobject_int(msg->value.as_array.values[c++], true, &rc); + RCGO(rc, finish); + + rc = jbl_from_json(&jbl, json); + RCGO(rc, finish); + + if (id > 0) { + rc = ejdb_put(db, coll, jbl, id); + } else { + rc = ejdb_put_new(db, coll, jbl, &id); + } + RCGO(rc, finish); + + rv1.type = Dart_CObject_kInt64; + rv1.value.as_int64 = id; + result.type = Dart_CObject_kArray; + result.value.as_array.length = sizeof(rv) / sizeof(rv[0]); + result.value.as_array.values = rv; + +finish: + if (jbl) { + jbl_destroy(&jbl); + } + EJPORT_RC(&result, rc); + Dart_PostCObject(reply_port, &result); +} + +static void ejd_del_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port) { + iwrc rc = 0; + Dart_CObject result = { .type = Dart_CObject_kArray }; + + int c = 2; + if ((msg->type != Dart_CObject_kArray) || (msg->value.as_array.length != 3 + c)) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + intptr_t ptr = cobject_int(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + EJDB2Handle *dbh = (EJDB2Handle*) ptr; + if (!dbh || !dbh->db) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + EJDB db = dbh->db; + const char *coll = cobject_str(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + int64_t id = cobject_int(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + rc = ejdb_del(db, coll, id); + +finish: + EJPORT_RC(&result, rc); + Dart_PostCObject(reply_port, &result); +} + +static void ejd_rename_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port) { + iwrc rc = 0; + Dart_CObject result = { .type = Dart_CObject_kArray }; + + int c = 2; + if ((msg->type != Dart_CObject_kArray) || (msg->value.as_array.length != 3 + c)) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + intptr_t ptr = cobject_int(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + EJDB2Handle *dbh = (EJDB2Handle*) ptr; + if (!dbh || !dbh->db) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + EJDB db = dbh->db; + + const char *oname = cobject_str(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + const char *nname = cobject_str(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + rc = ejdb_rename_collection(db, oname, nname); + +finish: + EJPORT_RC(&result, rc); + Dart_PostCObject(reply_port, &result); +} + +static void ejd_idx_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port) { + iwrc rc = 0; + Dart_CObject result = { .type = Dart_CObject_kArray }; + + int c = 2; + if ((msg->type != Dart_CObject_kArray) || (msg->value.as_array.length != 4 + c)) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + intptr_t ptr = cobject_int(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + EJDB2Handle *dbh = (EJDB2Handle*) ptr; + if (!dbh || !dbh->db) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + EJDB db = dbh->db; + const char *coll = cobject_str(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + const char *path = cobject_str(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + int64_t mode = cobject_int(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + rc = ejdb_ensure_index(db, coll, path, mode); + +finish: + EJPORT_RC(&result, rc); + Dart_PostCObject(reply_port, &result); +} + +static void ejd_rmi_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port) { + iwrc rc = 0; + Dart_CObject result = { .type = Dart_CObject_kArray }; + + int c = 2; + if ((msg->type != Dart_CObject_kArray) || (msg->value.as_array.length != 4 + c)) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + intptr_t ptr = cobject_int(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + EJDB2Handle *dbh = (EJDB2Handle*) ptr; + if (!dbh || !dbh->db) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + EJDB db = dbh->db; + const char *coll = cobject_str(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + const char *path = cobject_str(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + int64_t mode = cobject_int(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + rc = ejdb_remove_index(db, coll, path, mode); + +finish: + EJPORT_RC(&result, rc); + Dart_PostCObject(reply_port, &result); +} + +static void ejd_rmc_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port) { + iwrc rc = 0; + Dart_CObject result = { .type = Dart_CObject_kArray }; + + int c = 2; + if ((msg->type != Dart_CObject_kArray) || (msg->value.as_array.length != 2 + c)) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + intptr_t ptr = cobject_int(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + EJDB2Handle *dbh = (EJDB2Handle*) ptr; + if (!dbh || !dbh->db) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + EJDB db = dbh->db; + const char *coll = cobject_str(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + rc = ejdb_remove_collection(db, coll); + +finish: + EJPORT_RC(&result, rc); + Dart_PostCObject(reply_port, &result); +} + +static void ejd_bkp_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port) { + iwrc rc = 0; + Dart_CObject result, rv1; + Dart_CObject *rv[1] = { &rv1 }; + uint64_t ts = 0; + + int c = 2; + if ((msg->type != Dart_CObject_kArray) || (msg->value.as_array.length != 2 + c)) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + intptr_t ptr = cobject_int(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + EJDB2Handle *dbh = (EJDB2Handle*) ptr; + if (!dbh || !dbh->db) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + EJDB db = dbh->db; + const char *fileName = cobject_str(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + rc = ejdb_online_backup(db, &ts, fileName); + + rv1.type = Dart_CObject_kInt64; + rv1.value.as_int64 = ts; + result.type = Dart_CObject_kArray; + result.value.as_array.length = sizeof(rv) / sizeof(rv[0]); + result.value.as_array.values = rv; + +finish: + EJPORT_RC(&result, rc); + Dart_PostCObject(reply_port, &result); +} + +static void ejd_info_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port) { + iwrc rc = 0; + Dart_CObject result, rv1; + Dart_CObject *rv[1] = { &rv1 }; + + JBL jbl = 0; + IWXSTR *xstr = 0; + int c = 2; + + if ((msg->type != Dart_CObject_kArray) || (msg->value.as_array.length != 1 + c)) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + intptr_t ptr = cobject_int(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + EJDB2Handle *dbh = (EJDB2Handle*) ptr; + if (!dbh || !dbh->db) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + EJDB db = dbh->db; + rc = ejdb_get_meta(db, &jbl); + RCGO(rc, finish); + + xstr = iwxstr_new2(jbl_size(jbl) * 2); + if (!xstr) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, 0); + RCGO(rc, finish); + + rv1.type = Dart_CObject_kString; + rv1.value.as_string = iwxstr_ptr(xstr); + result.type = Dart_CObject_kArray; + result.value.as_array.length = sizeof(rv) / sizeof(rv[0]); + result.value.as_array.values = rv; + +finish: + if (jbl) { + jbl_destroy(&jbl); + } + EJPORT_RC(&result, rc); + Dart_PostCObject(reply_port, &result); + if (xstr) { + iwxstr_destroy(xstr); + } +} + +static void ejd_get_wrapped(Dart_Port receive_port, Dart_CObject *msg, Dart_Port reply_port) { + iwrc rc = 0; + Dart_CObject result, rv1; + Dart_CObject *rv[1] = { &rv1 }; + + JBL jbl = 0; + IWXSTR *xstr = 0; + int c = 2; + + if ((msg->type != Dart_CObject_kArray) || (msg->value.as_array.length != 3 + c)) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + intptr_t ptr = cobject_int(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + EJDB2Handle *dbh = (EJDB2Handle*) ptr; + if (!dbh || !dbh->db) { + rc = EJD_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + EJDB db = dbh->db; + const char *coll = cobject_str(msg->value.as_array.values[c++], false, &rc); + RCGO(rc, finish); + + int64_t id = cobject_int(msg->value.as_array.values[c++], true, &rc); + RCGO(rc, finish); + + rc = ejdb_get(db, coll, id, &jbl); + RCGO(rc, finish); + + xstr = iwxstr_new2(jbl_size(jbl) * 2); + if (!xstr) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, 0); + RCGO(rc, finish); + + rv1.type = Dart_CObject_kString; + rv1.value.as_string = iwxstr_ptr(xstr); + result.type = Dart_CObject_kArray; + result.value.as_array.length = sizeof(rv) / sizeof(rv[0]); + result.value.as_array.values = rv; + +finish: + if (jbl) { + jbl_destroy(&jbl); + } + EJPORT_RC(&result, rc); + Dart_PostCObject(reply_port, &result); + if (xstr) { + iwxstr_destroy(xstr); + } +} + +/////////////////////////////////////////////////////////////////////////// +// +/////////////////////////////////////////////////////////////////////////// + +static const char *ejd_ecodefn(locale_t locale, uint32_t ecode) { + if (!((ecode > _EJD_ERROR_START) && (ecode < _EJD_ERROR_END))) { + return 0; + } + switch (ecode) { + case EJD_ERROR_CREATE_PORT: + return "Failed to create a Dart port (EJD_ERROR_CREATE_PORT)"; + case EJD_ERROR_INVALID_NATIVE_CALL_ARGS: + return "Invalid native function call args (EJD_ERROR_INVALID_NATIVE_CALL_ARGS)"; + case EJD_ERROR_INVALID_STATE: + return "Invalid native extension state (EJD_ERROR_INVALID_STATE)"; + case EJD_ERROR_POST_PORT: + return "Failed to post message to Dart port (EJD_ERROR_POST_PORT)"; + } + return 0; +} + +DART_EXPORT Dart_Handle ejdb2dart_Init(Dart_Handle parent_library) { + static volatile int ejd_ecodefn_initialized = 0; + if (__sync_bool_compare_and_swap(&ejd_ecodefn_initialized, 0, 1)) { + iwrc rc = ejdb_init(); + if (rc) { + return ejd_error_rc_create(rc); + } + iwlog_register_ecodefn(ejd_ecodefn); + } + if (Dart_IsError(parent_library)) { + return parent_library; + } + Dart_Handle dh = Dart_SetNativeResolver(parent_library, ejd_resolve_name, 0); + if (Dart_IsError(dh)) { + return dh; + } + return Dart_Null(); +} + +static void ejd_ctx_finalizer(void *isolate_callback_data, void *peer) { + EJDB2Context *ctx = peer; + if (ctx) { + if (ctx->dbh) { + iwrc rc = ejdb2_isolate_shared_release(&ctx->dbh); + if (rc) { + iwlog_ecode_error3(rc); + } + } + if (ctx->wph && Dart_CurrentIsolateGroup()) { + Dart_DeleteWeakPersistentHandle(ctx->wph); + } + } + + free(ctx); +} diff --git a/src/bindings/ejdb2_dart/lib/ejdb2_dart.dart b/src/bindings/ejdb2_dart/lib/ejdb2_dart.dart new file mode 100644 index 0000000..48988d8 --- /dev/null +++ b/src/bindings/ejdb2_dart/lib/ejdb2_dart.dart @@ -0,0 +1,632 @@ +/// +/// EJDB2 Dart VM native API binding. +/// +/// See https://github.com/Softmotions/ejdb/blob/master/README.md +/// +/// For API usage examples look into `/example` and `/test` folders. +/// + +library ejdb2_dart; + +import 'dart:async'; +import 'dart:convert'; +import 'dart:convert' as convert_lib; +import 'dart:io'; +import 'dart:isolate'; +import 'dart:nativewrappers' show NativeFieldWrapperClass2; + +import 'package:path/path.dart' as path_lib; +import 'package:quiver/core.dart'; +import 'package:json_at/json_at.dart'; + +import 'dart-ext:ejdb2dart'; + +String ejdb2ExplainRC(int rc) native 'explain_rc'; + +/// EJDB specific exception +class EJDB2Error implements Exception { + EJDB2Error(this.code, this.message); + + EJDB2Error.fromCode(int code) : this(code, ejdb2ExplainRC(code)); + + EJDB2Error.invalidState() : this.fromCode(EJD_ERROR_INVALID_STATE); + + EJDB2Error.notFound() : this.fromCode(IWKV_ERROR_NOTFOUND); + + static int EJD_ERROR_CREATE_PORT = 89001; + static int EJD_ERROR_POST_PORT = 89002; + static int EJD_ERROR_INVALID_NATIVE_CALL_ARGS = 89003; + static int EJD_ERROR_INVALID_STATE = 89004; + static int IWKV_ERROR_NOTFOUND = 75001; + + final int code; + + final String message; + + bool get notFound => code == IWKV_ERROR_NOTFOUND; + + bool get invalidQuery => code == 87001; + + @override + String toString() => '$runtimeType: $code $message'; +} + +/// EJDB document item +class JBDOC { + JBDOC(this.id, this._json); + JBDOC._fromList(List list) : this(list[0] as int, list[1] as String?); + + /// Document identifier + final int id; + + /// Document body as JSON string + String get json => _json ?? convert_lib.jsonEncode(_object); + + /// Document body as parsed JSON object. + dynamic get object { + if (_json == null) { + return _object; + } else { + _object = convert_lib.jsonDecode(_json!); + _json = null; // Release memory used to store JSON string data + return _object; + } + } + + /// Gets subset of document using RFC 6901 JSON [pointer]. + Optional at(String pointer) => jsonAt(object, pointer); + + /// Gets subset of document using RFC 6901 JSON [pointer]. + Optional operator [](String pointer) => at(pointer); + + String? _json; + + dynamic _object; + + @override + String toString() => '$runtimeType: $id $json'; +} + +/// Represents query on ejdb collection. +/// Instance can be reused for multiple queries reusing +/// placeholder parameters. +class JQL extends NativeFieldWrapperClass2 { + JQL._(this.db, this.query, this.collection); + + final String query; + final String collection; + final EJDB2 db; + + StreamController? _controller; + RawReceivePort? _replyPort; + + /// Execute query and returns a stream of matched documents. + /// + /// [explainCallback] Used to get query execution log. + /// [limit] Overrides `limit` set by query text for this execution session. + /// + Stream execute({void explainCallback(String log)?, int limit = 0}) { + abort(); + var execHandle = 0; + _controller = StreamController(); + _replyPort = RawReceivePort(); + _replyPort!.handler = (dynamic reply) { + if (reply is int) { + _exec_check(execHandle, true); + _replyPort!.close(); + _controller!.addError(EJDB2Error.fromCode(reply)); + return; + } else if (reply is List) { + _exec_check(execHandle, false); + if (reply[2] != null && explainCallback != null) { + explainCallback(reply[2] as String); + } + _controller!.add(JBDOC._fromList(reply)); + } else { + _exec_check(execHandle, true); + if (reply != null && explainCallback != null) { + explainCallback(reply as String); + } + abort(); + } + }; + execHandle = _exec(_replyPort!.sendPort, explainCallback != null, limit); + return _controller!.stream; + } + + /// Returns optional element for first record in result set. + Future> first({void explainCallback(String log)?}) async { + await for (final doc in execute(explainCallback: explainCallback, limit: 1)) { + return Optional.of(doc); + } + return const Optional.absent(); + } + + /// Return first record in result set or throw not found [EJDB2Error] error. + Future firstRequired({void explainCallback(String log)?}) async { + await for (final doc in execute(explainCallback: explainCallback, limit: 1)) { + return doc; + } + throw EJDB2Error.notFound(); + } + + /// Collects up to [n] elements from result set into array. + Future> firstN(int n, {void explainCallback(String log)?}) async { + final ret = []; + await for (final doc in execute(explainCallback: explainCallback, limit: n)) { + if (n-- <= 0) break; + ret.add(doc); + } + return ret; + } + + /// Abort query execution. + void abort() { + _replyPort?.close(); + _replyPort = null; + _controller?.close(); + _controller = null; + } + + /// Return scalar integer value as result of query execution. + /// For example execution of count query: `/... | count` + Future scalarInt({void explainCallback(String log)?}) { + return execute(explainCallback: explainCallback).map((d) => d.id).first; + } + + /// Set [json] at the specified [placeholder]. + /// [placeholder] can be either `string` or `int` + JQL setJson(dynamic placeholder, dynamic json) { + _checkPlaceholder(placeholder); + ArgumentError.checkNotNull(json); + _set(placeholder, _asJsonString(json), 1); + return this; + } + + /// Set [regexp] at the specified [placeholder]. + /// [placeholder] can be either `string` or `int` + JQL setRegExp(dynamic placeholder, RegExp regexp) { + _checkPlaceholder(placeholder); + ArgumentError.checkNotNull(regexp); + _set(placeholder, regexp.pattern, 2); + return this; + } + + /// Set integer [val] at the specified [placeholder]. + /// [placeholder] can be either `string` or `int` + JQL setInt(dynamic placeholder, int val) { + _checkPlaceholder(placeholder); + ArgumentError.checkNotNull(val); + _set(placeholder, val); + return this; + } + + /// Set double [val] at the specified [placeholder]. + /// [placeholder] can be either `string` or `int` + JQL setDouble(dynamic placeholder, double val) { + _checkPlaceholder(placeholder); + ArgumentError.checkNotNull(val); + _set(placeholder, val); + return this; + } + + /// Set boolean [val] at the specified [placeholder]. + /// [placeholder] can be either `string` or `int` + JQL setBoolean(dynamic placeholder, bool val) { + _checkPlaceholder(placeholder); + ArgumentError.checkNotNull(val); + _set(placeholder, val); + return this; + } + + /// Set string [val] at the specified [placeholder]. + /// [placeholder] can be either `string` or `int` + JQL setString(dynamic placeholder, String val) { + _checkPlaceholder(placeholder); + ArgumentError.checkNotNull(val); + _set(placeholder, val); + return this; + } + + /// Set `null` at the specified [placeholder]. + /// [placeholder] can be either `string` or `int` + JQL setNull(dynamic placeholder) { + _checkPlaceholder(placeholder); + _set(placeholder, null); + return this; + } + + /// Get current `limit` encoded in query. + int get limit native 'jql_get_limit'; + + void _checkPlaceholder(dynamic placeholder) { + if (!(placeholder is String) && !(placeholder is int)) { + ArgumentError.value(placeholder, 'placeholder'); + } + } + + void _set(dynamic placeholder, dynamic value, [int type]) native 'jql_set'; + + int _exec(SendPort sendPort, bool explain, int limit) native 'exec'; + + void _exec_check(int execHandle, bool terminate) native 'check_exec'; +} + +/// Database wrapper +class EJDB2 extends NativeFieldWrapperClass2 { + EJDB2._(); + + static bool _checkCompleterPortError(Completer completer, dynamic reply) { + if (reply is int) { + completer.completeError(EJDB2Error(reply, ejdb2ExplainRC(reply))); + return true; + } else if (reply is! List) { + completer.completeError(EJDB2Error(0, 'Invalid port response')); + return true; + } + return false; + } + + /// Open EJDB2 database + /// See https://github.com/Softmotions/ejdb/blob/master/src/ejdb2.h#L104 + /// for description of options. + static Future open(String path, + {bool truncate = false, + bool readonly = false, + bool http_enabled = false, + bool http_read_anon = false, + bool wal_enabled = true, + bool wal_check_crc_on_checkpoint = false, + int? wal_checkpoint_buffer_sz, + int? wal_checkpoint_timeout_sec, + int? wal_savepoint_timeout_sec, + int? wal_wal_buffer_sz, + int? document_buffer_sz, + int? sort_buffer_sz, + String? http_access_token, + String? http_bind, + int? http_max_body_size, + int? http_port}) { + final completer = Completer(); + final replyPort = RawReceivePort(); + final jb = EJDB2._(); + + path = path_lib.canonicalize(File(path).absolute.path); + + replyPort.handler = (dynamic reply) { + replyPort.close(); + if (_checkCompleterPortError(completer, reply)) { + return; + } + try { + jb._set_handle((reply as List).first as int); + } catch (e) { + completer.completeError(e); + return; + } + completer.complete(jb); + }; + + // Open + var oflags = 0; + if (readonly) { + oflags |= 0x02; + } else if (truncate) { + oflags |= 0x04; + } + + jb._port().send([ + replyPort.sendPort, + 'open', + path, + oflags, + wal_enabled, + wal_check_crc_on_checkpoint, + wal_checkpoint_buffer_sz as dynamic, + wal_checkpoint_timeout_sec as dynamic, + wal_savepoint_timeout_sec as dynamic, + wal_wal_buffer_sz as dynamic, + document_buffer_sz as dynamic, + sort_buffer_sz as dynamic, + http_enabled, + http_access_token as dynamic, + http_bind as dynamic, + http_max_body_size as dynamic, + http_port as dynamic, + http_read_anon + ]); + return completer.future; + } + + /// Closes database instance. + Future close() { + final hdb = _get_handle(); + if (hdb == null) { + return Future.value(); + } + final completer = Completer(); + final replyPort = RawReceivePort(); + replyPort.handler = (dynamic reply) { + replyPort.close(); + if (_checkCompleterPortError(completer, reply)) { + return; + } + completer.complete(); + }; + _set_handle(null); + _port().send([replyPort.sendPort, 'close', hdb as dynamic]); + return completer.future; + } + + /// Save [json] document under specified [id] or create a document + /// with new generated `id`. Returns future holding actual document `id`. + Future put(String collection, dynamic json, [int? id]) { + final hdb = _get_handle(); + if (hdb == null) { + return Future.error(EJDB2Error.invalidState()); + } + final completer = Completer(); + final replyPort = RawReceivePort(); + replyPort.handler = (dynamic reply) { + replyPort.close(); + if (_checkCompleterPortError(completer, reply)) { + return; + } + completer.complete((reply as List).first as int); + }; + _port().send([ + replyPort.sendPort, + 'put', + hdb as dynamic, + collection, + _asJsonString(json), + id as dynamic + ]); + return completer.future; + } + + /// Apply rfc6902/rfc7386 JSON [patch] to the document identified by [id]. + Future patch(String collection, dynamic patchObj, int id, [bool upsert = false]) { + final hdb = _get_handle(); + if (hdb == null) { + return Future.error(EJDB2Error.invalidState()); + } + final completer = Completer(); + final replyPort = RawReceivePort(); + replyPort.handler = (dynamic reply) { + replyPort.close(); + if (_checkCompleterPortError(completer, reply)) { + return; + } + completer.complete(); + }; + _port().send([ + replyPort.sendPort, + 'patch', + hdb as dynamic, + collection, + _asJsonString(patchObj), + id as dynamic, + upsert + ]); + return completer.future; + } + + /// Apply JSON merge patch (rfc7396) to the document identified by `id` or + /// insert new document under specified `id`. + Future patchOrPut(String collection, dynamic patchObj, int id) => + patch(collection, patch, id, true); + + /// Get json body of document identified by [id] and stored in [collection]. + /// Throws [EJDB2Error] not found exception if document is not found. + Future get(String collection, int id) { + final hdb = _get_handle(); + if (hdb == null) { + return Future.error(EJDB2Error.invalidState()); + } + final completer = Completer(); + final replyPort = RawReceivePort(); + replyPort.handler = (dynamic reply) { + replyPort.close(); + if (_checkCompleterPortError(completer, reply)) { + return; + } + completer.complete((reply as List).first as String); + }; + _port().send([replyPort.sendPort, 'get', hdb as dynamic, collection, id]); + return completer.future; + } + + /// Get json body of database metadata. + Future info() { + final hdb = _get_handle(); + if (hdb == null) { + return Future.error(EJDB2Error.invalidState()); + } + final completer = Completer(); + final replyPort = RawReceivePort(); + replyPort.handler = (dynamic reply) { + replyPort.close(); + if (_checkCompleterPortError(completer, reply)) { + return; + } + completer.complete((reply as List).first as String); + }; + _port().send([replyPort.sendPort, 'info', hdb as dynamic]); + return completer.future; + } + + /// Remove document idenfied by [id] from [collection]. + Future del(String collection, int id) { + final hdb = _get_handle(); + if (hdb == null) { + return Future.error(EJDB2Error.invalidState()); + } + final completer = Completer(); + final replyPort = RawReceivePort(); + replyPort.handler = (dynamic reply) { + replyPort.close(); + if (_checkCompleterPortError(completer, reply)) { + return; + } + completer.complete(); + }; + _port().send([replyPort.sendPort, 'del', hdb as dynamic, collection, id]); + return completer.future; + } + + /// Remove document idenfied by [id] from [collection]. + /// Doesn't raise error if document is not found. + Future delIgnoreNotFound(String collection, int id) => + del(collection, id).catchError((err) { + if (err is EJDB2Error && err.notFound) { + return Future.value(); + } else { + return Future.error(err as Object); + } + }); + + Future renameCollection(String oldCollection, String newCollectionName) { + final hdb = _get_handle(); + if (hdb == null) { + return Future.error(EJDB2Error.invalidState()); + } + final completer = Completer(); + final replyPort = RawReceivePort(); + replyPort.handler = (dynamic reply) { + replyPort.close(); + if (_checkCompleterPortError(completer, reply)) { + return; + } + completer.complete(); + }; + _port().send([replyPort.sendPort, 'rename', hdb as dynamic, oldCollection, newCollectionName]); + return completer.future; + } + + /// Ensures json document database index specified by [path] json pointer to string data type. + Future ensureStringIndex(String collection, String path, {bool unique = false}) { + return _idx(collection, path, 0x04 | (unique ? 0x01 : 0)); + } + + /// Removes specified database index. + Future removeStringIndex(String collection, String path, {bool unique = false}) { + return _rmi(collection, path, 0x04 | (unique ? 0x01 : 0)); + } + + /// Ensures json document database index specified by [path] json pointer to integer data type. + Future ensureIntIndex(String collection, String path, {bool unique = false}) { + return _idx(collection, path, 0x08 | (unique ? 0x01 : 0)); + } + + /// Removes specified database index. + Future removeIntIndex(String collection, String path, {bool unique = false}) { + return _rmi(collection, path, 0x08 | (unique ? 0x01 : 0)); + } + + /// Ensures json document database index specified by [path] json pointer to floating point data type. + Future ensureFloatIndex(String collection, String path, {bool unique = false}) { + return _idx(collection, path, 0x10 | (unique ? 0x01 : 0)); + } + + /// Removes specified database index. + Future removeFloatIndex(String collection, String path, {bool unique = false}) { + return _rmi(collection, path, 0x10 | (unique ? 0x01 : 0)); + } + + /// Removes database [collection]. + Future removeCollection(String collection) { + final hdb = _get_handle(); + if (hdb == null) { + return Future.error(EJDB2Error.invalidState()); + } + final completer = Completer(); + final replyPort = RawReceivePort(); + replyPort.handler = (dynamic reply) { + replyPort.close(); + if (_checkCompleterPortError(completer, reply)) { + return; + } + completer.complete(); + }; + _port().send([replyPort.sendPort, 'rmc', hdb as dynamic, collection]); + return completer.future; + } + + /// Creates an online database backup image and copies it into the specified [fileName]. + /// During online backup phase read/write database operations are allowed and not + /// blocked for significant amount of time. Returns future with backup + /// finish time as number of milliseconds since epoch. + Future onlineBackup(String fileName) { + final hdb = _get_handle(); + if (hdb == null) { + return Future.error(EJDB2Error.invalidState()); + } + final completer = Completer(); + final replyPort = RawReceivePort(); + replyPort.handler = (dynamic reply) { + replyPort.close(); + if (_checkCompleterPortError(completer, reply)) { + return; + } + completer.complete((reply as List).first as int); + }; + _port().send([replyPort.sendPort, 'bkp', hdb as dynamic, fileName]); + return completer.future; + } + + /// Create instance of [query] specified for [collection]. + /// If [collection] is not specified a [query] spec must contain collection name, + /// eg: `@mycollection/[foo=bar]` + JQL createQuery(String query, [String collection]) native 'create_query'; + + Future _idx(String collection, String path, int mode) { + final hdb = _get_handle(); + if (hdb == null) { + return Future.error(EJDB2Error.invalidState()); + } + final completer = Completer(); + final replyPort = RawReceivePort(); + replyPort.handler = (dynamic reply) { + replyPort.close(); + if (_checkCompleterPortError(completer, reply)) { + return; + } + completer.complete(); + }; + _port().send([replyPort.sendPort, 'idx', hdb as dynamic, collection, path, mode]); + return completer.future; + } + + Future _rmi(String collection, String path, int mode) { + final hdb = _get_handle(); + if (hdb == null) { + return Future.error(EJDB2Error.invalidState()); + } + final completer = Completer(); + final replyPort = RawReceivePort(); + replyPort.handler = (dynamic reply) { + replyPort.close(); + if (_checkCompleterPortError(completer, reply)) { + return; + } + completer.complete(); + }; + _port().send([replyPort.sendPort, 'rmi', hdb as dynamic, collection, path, mode]); + return completer.future; + } + + SendPort _port() native 'port'; + + void _set_handle(int? handle) native 'set_handle'; + + int? _get_handle() native 'get_handle'; +} + +String _asJsonString(dynamic val) { + if (val is String) { + return val; + } else { + return jsonEncode(val); + } +} diff --git a/src/bindings/ejdb2_dart/pubspec.yaml b/src/bindings/ejdb2_dart/pubspec.yaml new file mode 100644 index 0000000..c2d9bc4 --- /dev/null +++ b/src/bindings/ejdb2_dart/pubspec.yaml @@ -0,0 +1,15 @@ +name: ejdb2_dart +description: Embeddable JSON Database engine EJDB http://ejdb.org Dart binding. +version: 1.0.63 +homepage: https://github.com/Softmotions/ejdb +issue_tracker: https://github.com/Softmotions/ejdb/issues?q=is%3Aissue+label%3ADart+ +license: MIT + +environment: + sdk: ">=2.12.0-0 <3.0.0" + +dependencies: + json_at: ^2.0.0-nullsafety.1 + path: ^1.8.0-nullsafety.3 + quiver: ^3.0.0-nullsafety.2 + test: ^1.16.0-nullsafety.13 diff --git a/src/bindings/ejdb2_dart/pubspec.yaml.in b/src/bindings/ejdb2_dart/pubspec.yaml.in new file mode 100644 index 0000000..c6a9ffa --- /dev/null +++ b/src/bindings/ejdb2_dart/pubspec.yaml.in @@ -0,0 +1,15 @@ +name: ejdb2_dart +description: Embeddable JSON Database engine EJDB http://ejdb.org Dart binding. +version: @EJDB2_DART_VERSION@ +homepage: https://github.com/Softmotions/ejdb +issue_tracker: https://github.com/Softmotions/ejdb/issues?q=is%3Aissue+label%3ADart+ +license: MIT + +environment: + sdk: ">=2.12.0-0 <3.0.0" + +dependencies: + json_at: ^2.0.0-nullsafety.1 + path: ^1.8.0-nullsafety.3 + quiver: ^3.0.0-nullsafety.2 + test: ^1.16.0-nullsafety.13 diff --git a/src/bindings/ejdb2_dart/test/ejdb2_dart_test.dart b/src/bindings/ejdb2_dart/test/ejdb2_dart_test.dart new file mode 100644 index 0000000..980d8f5 --- /dev/null +++ b/src/bindings/ejdb2_dart/test/ejdb2_dart_test.dart @@ -0,0 +1,192 @@ +import 'package:ejdb2_dart/ejdb2_dart.dart'; + +void main() async { + final db = await EJDB2.open('hello.db', truncate: true); + + var q = db.createQuery('@mycoll/*'); + assert(q.collection == 'mycoll'); + + var id = await db.put('mycoll', {'foo': 'bar'}); + assert(id == 1); + + dynamic error; + try { + await db.put('mycoll', '{"'); + } on EJDB2Error catch (e) { + error = e; + assert(e.code == 86005); + assert(e.message == 'Unquoted JSON string (JBL_ERROR_PARSE_UNQUOTED_STRING)'); + } + assert(error != null); + + var json = await db.get('mycoll', id); + assert(json == '{"foo":"bar"}'); + + await db.put('mycoll', {'foo': 'baz'}); + + final list = await db.createQuery('@mycoll/*').execute(limit: 1).toList(); + assert(list.length == 1); + + var first = await db.createQuery('@mycoll/*').first(); + assert(first.isPresent); + assert(first.value.json == '{"foo":"baz"}'); + + first = await db.createQuery('@mycoll/[zzz=bbb]').first(); + assert(first.isEmpty); + + var firstN = await db.createQuery('@mycoll/*').firstN(5); + assert(firstN.length == 2); + assert(firstN[0].json == '{"foo":"baz"}'); + assert(firstN[1].json == '{"foo":"bar"}'); + + firstN = await db.createQuery('@mycoll/*').firstN(1); + assert(firstN.length == 1); + + // Query 1 + final rbuf = []; + await for (final doc in q.execute()) { + rbuf..add(doc.id.toString())..add(doc.json); + } + assert(rbuf.toString() == '[2, {"foo":"baz"}, 1, {"foo":"bar"}]'); + rbuf.clear(); + + // Query 2 + await for (final doc in db.createQuery('@mycoll/[foo=zaz]').execute()) { + rbuf..add(doc.id.toString())..add(doc.json); + } + assert(rbuf.isEmpty); + + // Query 3 + await for (final doc in db.createQuery('/[foo=bar]', 'mycoll').execute()) { + rbuf..add(doc.id.toString())..add(doc.json); + } + assert(rbuf.toString() == '[1, {"foo":"bar"}]'); + + error = null; + try { + await db.createQuery('@mycoll/[').execute(); + } on EJDB2Error catch (e) { + error = e; + assert(e.invalidQuery); + assert(e.message.contains('@mycoll/[ <---')); + } + assert(error != null); + + var count = await db.createQuery('@mycoll/* | count').scalarInt(); + assert(count == 2); + + count = await db.createQuery('@mycoll/* | count').scalarInt(explainCallback: (log) { + log.contains('[INDEX] NO [COLLECTOR] PLAIN'); + }); + assert(count == 2); + + // Del + await db.del('mycoll', 1); + error = null; + try { + await db.get('mycoll', 1); + } on EJDB2Error catch (e) { + error = e; + assert(e.notFound); + assert(e.message.contains('IWKV_ERROR_NOTFOUND')); + } + assert(error != null); + + count = await db.createQuery('@mycoll/* | count').scalarInt(); + assert(count == 1); + + // Patch + await db.patch('mycoll', '[{"op":"add", "path":"/baz", "value":"qux"}]', 2); + json = await db.get('mycoll', 2); + assert(json == '{"foo":"baz","baz":"qux"}'); + + // DB Info + json = await db.info(); + assert(json.contains('"collections":[{"name":"mycoll","dbid":3,"rnum":1,"indexes":[]}]')); + + // Indexes + await db.ensureStringIndex('mycoll', '/foo', unique: true); + json = await db.info(); + assert(json.contains('"indexes":[{"ptr":"/foo","mode":5,"idbf":0,"dbid":4,"rnum":1}]')); + + // Test JQL set + JBDOC? doc = await db.createQuery('@mycoll/[foo=:?]').setString(0, 'baz').execute().first; + assert(doc.json == '{"foo":"baz","baz":"qux"}'); + + // Test explain log + await db.createQuery('@mycoll/[foo=:?]').setString(0, 'baz').execute(explainCallback: (log) { + assert( + log.contains("[INDEX] SELECTED UNIQUE|STR|1 /foo EXPR1: 'foo = :?' INIT: IWKV_CURSOR_EQ")); + }); + + doc = await db + .createQuery('@mycoll/[foo=:?] and /[baz=:?]') + .setString(0, 'baz') + .setString(1, 'qux') + .execute() + .first; + assert(doc.json == '{"foo":"baz","baz":"qux"}'); + + doc = await db + .createQuery('@mycoll/[foo=:foo] and /[baz=:baz]') + .setString('foo', 'baz') + .setString('baz', 'qux') + .execute() + .first; + + doc = await db.createQuery('@mycoll/[foo re :?]').setRegExp(0, RegExp('.*')).execute().first; + assert(doc.json == '{"foo":"baz","baz":"qux"}'); + + doc = await db.createQuery('@mycoll/* | /foo').execute().first; + assert(doc.json == '{"foo":"baz"}'); + + await db.removeStringIndex('mycoll', '/foo', unique: true); + json = await db.info(); + assert(json.contains('"collections":[{"name":"mycoll","dbid":3,"rnum":1,"indexes":[]}]')); + + // Remove collection + await db.removeCollection('mycoll'); + json = await db.info(); + assert(json.contains('"collections":[]')); + + // Check apply + await db.put( + 'apply1', {'tx_hash': 'ed36cfd14a4fe29b16c511d68a8be89e42dcc6e4ced69d04f318448a2b8fafa0'}); + doc = await db + .createQuery('/[tx_hash = :?] | apply :?', 'apply1') + .setString(0, 'ed36cfd14a4fe29b16c511d68a8be89e42dcc6e4ced69d04f318448a2b8fafa0') + .setJson(1, {'status': 'completed'}).firstRequired(); + assert(doc.object['status'] == 'completed'); + + /// Test get limit + q = db.createQuery('@c1/* | limit 1'); + assert(q.limit == 1); + + q = db.createQuery('@c1/*'); + assert(q.limit == 0); + + id = await db.put('cc1', {'foo': 1}); + await db.renameCollection('cc1', 'cc2'); + json = await db.get('cc2', id); + assert(json == '{"foo":1}'); + + for (var i = 0; i < 10000; ++i) { + await db.put('cc1', {'name': 'v${i}'}); + } + var cnt = 0; + await for (final _ in db.createQuery('@cc1/*').execute()) { + cnt++; + } + assert(cnt == 10000); + + final ts0 = DateTime.now().millisecondsSinceEpoch; + final ts = await db.onlineBackup('hello-bkp.db'); + assert(ts > ts0); + await db.close(); + + // Reopen backup image + final db2 = await EJDB2.open('hello-bkp.db', truncate: false); + doc = (await db2.createQuery('@cc1/*').first()).orNull; + assert(doc != null); + await db2.close(); +} diff --git a/src/bindings/ejdb2_dart/version.txt b/src/bindings/ejdb2_dart/version.txt new file mode 100644 index 0000000..b0d4e4c --- /dev/null +++ b/src/bindings/ejdb2_dart/version.txt @@ -0,0 +1 @@ +1-nullsafety.1 \ No newline at end of file diff --git a/src/bindings/ejdb2_flutter/.gitignore b/src/bindings/ejdb2_flutter/.gitignore new file mode 100644 index 0000000..5d88f49 --- /dev/null +++ b/src/bindings/ejdb2_flutter/.gitignore @@ -0,0 +1,12 @@ +local.properties +!*.iml +.classpath +!.project +.DS_Store +.dart_tool/ +.packages +.pub/ +build/ +.flutter-plugins-dependencies +Podfile.lock + diff --git a/src/bindings/ejdb2_flutter/.metadata b/src/bindings/ejdb2_flutter/.metadata new file mode 100644 index 0000000..8536f75 --- /dev/null +++ b/src/bindings/ejdb2_flutter/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 68587a0916366e9512a78df22c44163d041dd5f3 + channel: stable + +project_type: plugin diff --git a/src/bindings/ejdb2_flutter/CHANGELOG.md b/src/bindings/ejdb2_flutter/CHANGELOG.md new file mode 100644 index 0000000..1605127 --- /dev/null +++ b/src/bindings/ejdb2_flutter/CHANGELOG.md @@ -0,0 +1,5 @@ +## @EJDB2_FLUTTER_VERSION@ + + - Used new versioning scheme: {EJDB_VERSION}{BINDING_VERSION_NUMBER} + - Opted out null safety mode + diff --git a/src/bindings/ejdb2_flutter/CMakeLists.txt b/src/bindings/ejdb2_flutter/CMakeLists.txt new file mode 100644 index 0000000..ee29ace --- /dev/null +++ b/src/bindings/ejdb2_flutter/CMakeLists.txt @@ -0,0 +1,62 @@ +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/version.txt _VERSION) +set_property(GLOBAL PROPERTY EJDB2_FLUTTER_VERSION_PROPERTY + "${PROJECT_VERSION}${_VERSION}") +set(EJDB2_FLUTTER_VERSION "${PROJECT_VERSION}${_VERSION}") + + +file( + COPY . + DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + REGEX "(/\\.gradle)|(/node_modules)|(/build$)|(/libs)|(/jniLibs)" EXCLUDE +) + +set(ANDROID_LIBS_DIR "${CMAKE_CURRENT_BINARY_DIR}/android/src/main/jniLibs") +set(PUB_PUBLISH_DIR "${CMAKE_CURRENT_BINARY_DIR}/pub_publish") + +foreach (AABI IN ITEMS ${ANDROID_ABIS}) + list(APPEND ANDROID_ABIS_LIBS "android_${AABI}") +endforeach() + +add_custom_target( + ejdb2_flutter ALL + DEPENDS ${ANDROID_ABIS_LIBS} + COMMAND ${CMAKE_COMMAND} -E remove_directory ${ANDROID_LIBS_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/../ejdb2_android/libs ${ANDROID_LIBS_DIR} + + # Prepare pub publish dir: + + COMMAND ${CMAKE_COMMAND} -E make_directory ${PUB_PUBLISH_DIR} + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/LICENSE ${PUB_PUBLISH_DIR} + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/README.md ${PUB_PUBLISH_DIR} + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/analysis_options.yaml ${PUB_PUBLISH_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/lib ${PUB_PUBLISH_DIR}/lib + + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/ios ${PUB_PUBLISH_DIR}/ios + + COMMAND ${CMAKE_COMMAND} -E make_directory ${PUB_PUBLISH_DIR}/android + COMMAND ${CMAKE_COMMAND} -E make_directory ${PUB_PUBLISH_DIR}/android/src + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/android/gradlew* ${PUB_PUBLISH_DIR}/android + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/android/*.gradle ${PUB_PUBLISH_DIR}/android + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/android/gradle.properties ${PUB_PUBLISH_DIR}/android + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/android/src ${PUB_PUBLISH_DIR}/android/src + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/android/gradle ${PUB_PUBLISH_DIR}/android/gradle + + COMMAND ${CMAKE_COMMAND} -E make_directory ${PUB_PUBLISH_DIR}/example + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/example/ios ${PUB_PUBLISH_DIR}/example/ios + + COMMAND ${CMAKE_COMMAND} -E make_directory ${PUB_PUBLISH_DIR}/example/android + COMMAND ${CMAKE_COMMAND} -E make_directory ${PUB_PUBLISH_DIR}/example/android/app + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/example/*.md ${PUB_PUBLISH_DIR}/example + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/example/*.yaml ${PUB_PUBLISH_DIR}/example + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/example/lib ${PUB_PUBLISH_DIR}/example/lib + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/example/test_driver ${PUB_PUBLISH_DIR}/example/test_driver + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/example/android/*.gradle ${PUB_PUBLISH_DIR}/example/android + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/example/android/gradlew* ${PUB_PUBLISH_DIR}/example/android + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/example/android/gradle.properties ${PUB_PUBLISH_DIR}/example/android + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/example/android/app/*.gradle ${PUB_PUBLISH_DIR}/example/android/app + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/example/android/app/src ${PUB_PUBLISH_DIR}/example/android/app/src + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/example/android/gradle ${PUB_PUBLISH_DIR}/example/android/gradle +) + +configure_file(pubspec.yaml.in ${PUB_PUBLISH_DIR}/pubspec.yaml @ONLY) +configure_file(CHANGELOG.md ${PUB_PUBLISH_DIR}/CHANGELOG.md @ONLY) \ No newline at end of file diff --git a/src/bindings/ejdb2_flutter/LICENSE b/src/bindings/ejdb2_flutter/LICENSE new file mode 100644 index 0000000..90d6fd3 --- /dev/null +++ b/src/bindings/ejdb2_flutter/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2012-2021 Softmotions Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/bindings/ejdb2_flutter/README.md b/src/bindings/ejdb2_flutter/README.md new file mode 100644 index 0000000..d348604 --- /dev/null +++ b/src/bindings/ejdb2_flutter/README.md @@ -0,0 +1,101 @@ +# EJDB2 Flutter integration + +Embeddable JSON Database engine http://ejdb.org Dart binding. + +See https://github.com/Softmotions/ejdb/blob/master/README.md + +For API usage examples take a look into [/example](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_flutter/example) +application. + +## Example + +pubspec.yaml + +```yaml +dependencies: + ejdb2_flutter: +``` + +```dart +import 'package:ejdb2_flutter/ejdb2_flutter.dart'; + +var db = await EJDB2Builder('test.db').open(); + +var id = await db.put('parrots', {'name': 'Bianca', 'age': 4}); + print('Bianca record: ${id}'); + +id = await db.put('parrots', {'name': 'Darko', 'age': 8}); +print('Darko record: ${id}'); + +final q = db.createQuery('/[age > :age]', 'parrots'); +await for (final doc in q.setInt('age', 3).execute()) { + print('Found ${doc}'); +} + +await db.close(); +``` + +## Supported platforms + +- iOS +- Android + +## iOS notes + +In order to build app with this binding you have +to add the following code into application `Podfile`: + +```ruby +pre_install do |installer| + # workaround for https://github.com/CocoaPods/CocoaPods/issues/3289 + Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {} +end +``` + +## Android notes + +For release builds you have to setup proguard rules as follows: + +`build.gradle` +``` + buildTypes { + ... + release { + ... + minifyEnabled true + useProguard true + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + } + } +``` + +`proguard-rules.pro` +``` +... + +# Keep EJDB +-keep class com.softmotions.ejdb2.** { *; } + +``` + +## How build it manually + +```sh +git clone https://github.com/Softmotions/ejdb.git + +cd ./ejdb +mkdir ./build && cd build + +cmake .. -DCMAKE_BUILD_TYPE=Release \ + -DANDROID_NDK_HOME= \ + -DBUILD_FLUTTER_BINDING=ON +make + +# Move generate to ejdb2 flutter pub package with example app +cd src/bindings/ejdb2_flutter/pub_publish +flutter pub get +cd ./example + +# Start Android emulator +flutter run +``` diff --git a/src/bindings/ejdb2_flutter/analysis_options.yaml b/src/bindings/ejdb2_flutter/analysis_options.yaml new file mode 100644 index 0000000..0a871c1 --- /dev/null +++ b/src/bindings/ejdb2_flutter/analysis_options.yaml @@ -0,0 +1,157 @@ +analyzer: + errors: + native_function_body_in_non_sdk_code: ignore + strong-mode: + implicit-casts: false + implicit-dynamic: true +linter: + rules: + - always_declare_return_types + - always_put_required_named_parameters_first + - always_require_non_null_named_parameters + - annotate_overrides + - avoid_bool_literals_in_conditional_expressions + - avoid_catching_errors + - avoid_classes_with_only_static_members + - avoid_double_and_int_checks + - avoid_empty_else + - avoid_field_initializers_in_const_classes + - avoid_implementing_value_types + - avoid_init_to_null + - avoid_js_rounded_ints + - avoid_null_checks_in_equality_operators + - avoid_private_typedef_functions + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + - avoid_returning_null + - avoid_returning_null_for_future + - avoid_returning_null_for_void + #- avoid_returning_this + - avoid_setters_without_getters + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + - avoid_types_as_parameter_names + - avoid_unused_constructor_parameters + - camel_case_extensions + - camel_case_types + - cancel_subscriptions + - comment_references + - control_flow_in_finally + - curly_braces_in_flow_control_structures + - diagnostic_describe_all_properties + - directives_ordering + - empty_catches + - empty_constructor_bodies + - empty_statements + - file_names + - flutter_style_todos + - hash_and_equals + - implementation_imports + - invariant_booleans + - iterable_contains_unrelated_type + - join_return_with_assignment + - library_names + - list_remove_unrelated_type + - literal_only_boolean_expressions + - no_adjacent_strings_in_list + - no_duplicate_case_values + - null_closures + - one_member_abstracts + - only_throw_errors + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_asserts_with_message + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_contains + - prefer_equal_for_default_values + - prefer_expression_function_bodies + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_for_elements_to_map_fromIterable + - prefer_foreach + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_iterable_whereType + - prefer_mixin + - prefer_null_aware_operators + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - prefer_void_to_null + - provide_deprecation_message + - recursive_getters + - slash_for_doc_comments + - sort_child_properties_last + - sort_constructors_first + - sort_pub_dependencies + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - type_init_formals + - unawaited_futures + - unnecessary_await_in_return + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_statements + - unnecessary_this + - unrelated_type_equality_checks + - unsafe_html + - use_full_hex_values_for_flutter_colors + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_string_buffers + - use_to_and_as_if_applicable + - valid_regexps + - void_checks + # - always_put_control_body_on_new_line + # - always_specify_types + # - avoid_annotating_with_dynamic + # - avoid_as + # - avoid_catches_without_on_clauses + # - avoid_equals_and_hash_code_on_mutable_classes + # - avoid_function_literals_in_foreach_calls + # - avoid_positional_boolean_parameters + # - avoid_print + # - avoid_types_on_closure_parameters + # - avoid_void_async + # - await_only_futures + # - cascade_invocations + # - close_sinks + # - constant_identifier_names + # - library_prefixes + # - lines_longer_than_80_chars + # - non_constant_identifier_names + # - omit_local_variable_types + # - parameter_assignments + # - prefer_const_constructors_in_immutables + # - prefer_double_quotes + # - prefer_if_elements_to_conditional_expressions + # - prefer_int_literals + # - public_member_api_docs + # - type_annotate_public_apis + # - unnecessary_brace_in_string_interps + # - use_function_type_syntax_for_parameters diff --git a/src/bindings/ejdb2_flutter/android/.gitignore b/src/bindings/ejdb2_flutter/android/.gitignore new file mode 100644 index 0000000..4707429 --- /dev/null +++ b/src/bindings/ejdb2_flutter/android/.gitignore @@ -0,0 +1,9 @@ +!*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +/libs diff --git a/src/bindings/ejdb2_flutter/android/.project b/src/bindings/ejdb2_flutter/android/.project new file mode 100644 index 0000000..90e475a --- /dev/null +++ b/src/bindings/ejdb2_flutter/android/.project @@ -0,0 +1,34 @@ + + + ejdb2_flutter + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + + 1607244372293 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/src/bindings/ejdb2_flutter/android/build.gradle b/src/bindings/ejdb2_flutter/android/build.gradle new file mode 100644 index 0000000..830e904 --- /dev/null +++ b/src/bindings/ejdb2_flutter/android/build.gradle @@ -0,0 +1,97 @@ +import org.gradle.plugins.ide.eclipse.model.Library + +group 'com.softmotions.ejdb2' +version '1.0' + +apply plugin: 'com.android.library' +//apply plugin: "com.greensopinion.gradle-android-eclipse" +//apply plugin: 'eclipse' + +sourceCompatibility = 1.8 + +ext { + + flutterRoot = { + def flutterProperties = new Properties() + def flutterPropertiesFile = rootProject.file('local.properties') + if (!flutterPropertiesFile.exists()) { + throw new GradleException("Flutter properties file not found. Define a flutter.properties file in your project root.") + } + flutterPropertiesFile.withInputStream { stream -> + flutterProperties.load(stream) + } + def flutterRoot = flutterProperties.getProperty('flutter.sdk') + if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the flutter.properties file.") + } + return flutterRoot + }() + + androidHome = { + if (System.env.ANDROID_HOME == null) { + throw new GradleException("Missing required ANDROID_HOME env variable"); + } + return System.env.ANDROID_HOME; + }() +} + + +buildscript { + repositories { + google() + jcenter() + maven { + url "https://plugins.gradle.org/m2/" + } + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + //classpath "gradle.plugin.com.greensopinion.gradle-android-eclipse:gradle-android-eclipse:1.1" + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + } +} + +android { + compileSdkVersion 28 + defaultConfig { + minSdkVersion 16 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + lintOptions { + disable 'InvalidPackage' + } +} + +dependencies { + compile fileTree(dir: 'src/main/jniLibs', include: ['*.jar']) +} + +// eclipse { +// classpath { +// plusConfigurations += [ configurations.compile, configurations.testCompile ] +// downloadSources = true +// file { +// whenMerged { +// def androidHome = project.ext.get('androidHome') +// def libAndroid = new Library(fileReference(file("$System.env.ANDROID_HOME/platforms/$project.android.compileSdkVersion/android.jar"))); +// libAndroid.setSourcePath(fileReference(file("$System.env.ANDROID_HOME/sources/$project.android.compileSdkVersion"))); +// // def flutterRoot = project.ext.get('flutterRoot') +// // def flutterLib = new Library(fileReference(file("$flutterRoot/bin/cache/artifacts/engine/android-x86/flutter.jar"))) +// // flutterLib.setSourcePath(fileReference(file('/home/adam/Projects/tmp/engine/shell/platform/android'))) +// // entries += [ libAndroid, flutterLib ] +// entries += [ libAndroid ] +// } +// } +// } +// } diff --git a/src/bindings/ejdb2_flutter/android/gradle.properties b/src/bindings/ejdb2_flutter/android/gradle.properties new file mode 100644 index 0000000..e30ec9d --- /dev/null +++ b/src/bindings/ejdb2_flutter/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1024M +android.useAndroidX=true +android.enableJetifier=true diff --git a/src/bindings/ejdb2_flutter/android/gradle/wrapper/gradle-wrapper.jar b/src/bindings/ejdb2_flutter/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5c2d1cf Binary files /dev/null and b/src/bindings/ejdb2_flutter/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src/bindings/ejdb2_flutter/android/gradle/wrapper/gradle-wrapper.properties b/src/bindings/ejdb2_flutter/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..f4d7b2b --- /dev/null +++ b/src/bindings/ejdb2_flutter/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/src/bindings/ejdb2_flutter/android/gradlew b/src/bindings/ejdb2_flutter/android/gradlew new file mode 100755 index 0000000..9d82f78 --- /dev/null +++ b/src/bindings/ejdb2_flutter/android/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/src/bindings/ejdb2_flutter/android/gradlew.bat b/src/bindings/ejdb2_flutter/android/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/src/bindings/ejdb2_flutter/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/bindings/ejdb2_flutter/android/settings.gradle b/src/bindings/ejdb2_flutter/android/settings.gradle new file mode 100644 index 0000000..f034e9f --- /dev/null +++ b/src/bindings/ejdb2_flutter/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'ejdb2_flutter' diff --git a/src/bindings/ejdb2_flutter/android/src/main/AndroidManifest.xml b/src/bindings/ejdb2_flutter/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7061af9 --- /dev/null +++ b/src/bindings/ejdb2_flutter/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/src/bindings/ejdb2_flutter/android/src/main/java/com/softmotions/ejdb2/Ejdb2FlutterPlugin.java b/src/bindings/ejdb2_flutter/android/src/main/java/com/softmotions/ejdb2/Ejdb2FlutterPlugin.java new file mode 100644 index 0000000..efa985e --- /dev/null +++ b/src/bindings/ejdb2_flutter/android/src/main/java/com/softmotions/ejdb2/Ejdb2FlutterPlugin.java @@ -0,0 +1,576 @@ +package com.softmotions.ejdb2; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import android.app.Activity; +import android.content.Context; +import android.util.Log; +import io.flutter.plugin.common.EventChannel; +import io.flutter.plugin.common.EventChannel.EventSink; +import io.flutter.plugin.common.EventChannel.StreamHandler; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugin.common.PluginRegistry.Registrar; + +/** + * Ejdb2FlutterPlugin + */ +public final class Ejdb2FlutterPlugin implements MethodCallHandler, StreamHandler { + + public static final String TAG = "Ejdb2FlutterPlugin"; + + @SuppressWarnings("StaticCollection") + static final Map dbmap = new ConcurrentHashMap<>(); + + static final AtomicInteger dbkeys = new AtomicInteger(); + + static final Map methods = new ConcurrentHashMap<>(); + + static final Executor executor = new ThreadPoolExecutor(0, 5, 60L, TimeUnit.SECONDS, + new SynchronousQueue()); + + static { + methods.put("executeFirst", thread(Ejdb2FlutterPlugin::executeFirst)); + methods.put("executeList", thread(Ejdb2FlutterPlugin::executeList)); + methods.put("executeScalarInt", thread(Ejdb2FlutterPlugin::executeScalarInt)); + methods.put("executeQuery", thread(Ejdb2FlutterPlugin::executeQuery)); + methods.put("onlineBackup", thread(Ejdb2FlutterPlugin::onlineBackup)); + methods.put("removeFloatIndex", thread(Ejdb2FlutterPlugin::removeFloatIndex)); + methods.put("ensureFloatIndex", thread(Ejdb2FlutterPlugin::ensureFloatIndex)); + methods.put("removeIntIndex", thread(Ejdb2FlutterPlugin::removeIntIndex)); + methods.put("ensureIntIndex", thread(Ejdb2FlutterPlugin::ensureIntIndex)); + methods.put("removeStringIndex", thread(Ejdb2FlutterPlugin::removeStringIndex)); + methods.put("ensureStringIndex", thread(Ejdb2FlutterPlugin::ensureStringIndex)); + methods.put("removeCollection", thread(Ejdb2FlutterPlugin::removeCollection)); + methods.put("renameCollection", thread(Ejdb2FlutterPlugin::renameCollection)); + methods.put("del", thread(Ejdb2FlutterPlugin::del)); + methods.put("get", thread(Ejdb2FlutterPlugin::get)); + methods.put("patch", thread(Ejdb2FlutterPlugin::patch)); + methods.put("put", thread(Ejdb2FlutterPlugin::put)); + methods.put("info", thread(Ejdb2FlutterPlugin::info)); + methods.put("open", thread(Ejdb2FlutterPlugin::open)); + methods.put("close", thread(Ejdb2FlutterPlugin::close)); + } + + Ejdb2FlutterPlugin(Registrar registrar) { + this.registrar = registrar; + this.methodChannel = new MethodChannel(registrar.messenger(), "ejdb2"); + this.methodChannel.setMethodCallHandler(this); + this.eventChannel = new EventChannel(registrar.messenger(), "ejdb2/query"); + this.eventChannel.setStreamHandler(this); + } + + final Registrar registrar; + + final MethodChannel methodChannel; + + final EventChannel eventChannel; + + EventSink events; + + static Ejdb2FlutterPlugin plugin; + + /** + * Plugin registration. + */ + public static void registerWith(Registrar registrar) { + plugin = new Ejdb2FlutterPlugin(registrar); + } + + @Override + public void onMethodCall(MethodCall call, Result result) { + DbMethod method = methods.get(call.method); + if (method == null) { + result.notImplemented(); + return; + } + @SuppressWarnings("unchecked") + Object[] args = ((List) call.arguments).toArray(); + DbEntry dbe = null; + if (args[0] != null) { + dbe = dbmap.get(((Number) args[0]).intValue()); + if (dbe == null) { + result.error("@ejdb", "Database handle already disposed", null); + return; + } + } + try { + method.handle(new DbMethodCall(this, dbe, args, result)); + } catch (EJDB2Exception e) { + result.error("@ejdb IWRC:" + e.getCode(), e.getMessage(), null); + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + result.error("@ejdb", e.toString(), null); + } + } + + @Override + public void onListen(Object arguments, EventSink events) { + this.events = events; + } + + @Override + public void onCancel(Object arguments) { + this.events = null; + } + + private static void executeFirst(DbMethodCall mc) throws Exception { + executeListImpl(mc, 1L); + } + + private static void executeList(DbMethodCall mc) throws Exception { + executeListImpl(mc, null); + } + + private static void executeListImpl(DbMethodCall mc, Long limit) throws Exception { + prepareQuery(mc, (q, hook) -> { + if (limit != null) { + q.setLimit(limit); + } + final List res = new ArrayList<>((limit != null && limit < 1024) ? limit.intValue() : 64); + q.execute((id, json) -> { + res.add(id); + res.add(json); + return 1; + }); + mc.successOnMainThread(res); + }); + } + + private static void executeScalarInt(DbMethodCall mc) throws Exception { + prepareQuery(mc, (q, hook) -> { + Long res = q.executeScalarInt(); + mc.successOnMainThread(res); + }); + } + + private static void executeQuery(DbMethodCall mc) throws Exception { + prepareQuery(mc, (q, hook) -> { + final AtomicReference> holder = new AtomicReference<>(new ArrayList<>()); + q.execute((id, json) -> { + List batch = holder.get(); + if (batch.size() >= 128 && batch.size() % 2 == 0) { + EventSink sink = mc.plugin.events; + if (sink != null) { + List res = batch; + res.add(0, hook); + mc.runOnMainThread(() -> sink.success(res)); + } + batch = new ArrayList<>(); + holder.set(batch); + } + batch.add(id); + batch.add(json); + return 1; + }); + // Flush rest of records + List batch = holder.get(); + batch.add(0, hook); + batch.add(true); + holder.set(null); + EventSink esink = mc.plugin.events; + if (esink != null) { + mc.runOnMainThread(() -> esink.success(batch)); + } + }); + } + + private static void prepareQuery(DbMethodCall mc, DbQueryHandler qh) throws Exception { + final String hook = cast(mc.args[1]); + final String coll = cast(mc.args[2]); + final String qtext = asString(mc.args[3], ""); + final Map qspec = cast(mc.args[4]); + final List> params = cast(mc.args[5]); + + final EJDB2 db = mc.getDb(); + final JQL q = db.createQuery(qtext, coll); + try { + for (Map.Entry entry : qspec.entrySet()) { + String key = entry.getKey(); + switch (key) { + case "l": // limit + q.setLimit(asLong(entry.getValue(), 0)); + break; + case "s": // skip + q.setSkip(asLong(entry.getValue(), 0)); + break; + } + } + for (List pslot : params) { + int type = asInt(pslot.get(0), 0); + Object plh = pslot.get(1); + Object val = pslot.get(2); + if (type == 0 || val == null) { + if (plh instanceof Number) { + q.setNull(((Number) plh).intValue()); + } else { + q.setNull((String) plh); + } + } else { + switch (type) { + case 1: // String + if (plh instanceof Number) { + q.setString(((Number) plh).intValue(), asString(val, null)); + } else { + q.setString((String) plh, asString(val, null)); + } + break; + case 2: // Long + if (plh instanceof Number) { + q.setLong(((Number) plh).intValue(), asLong(val, 0)); + } else { + q.setLong((String) plh, asLong(val, 0)); + } + break; + case 3: // Double + if (plh instanceof Number) { + q.setDouble(((Number) plh).intValue(), asDouble(val, 0)); + } else { + q.setDouble((String) plh, asDouble(val, 0)); + } + break; + case 4: // Boolean + if (plh instanceof Number) { + q.setBoolean(((Number) plh).intValue(), asBoolean(val, false)); + } else { + q.setBoolean((String) plh, asBoolean(val, false)); + } + break; + case 5: // Regexp + if (plh instanceof Number) { + q.setRegexp(((Number) plh).intValue(), asString(val, null)); + } else { + q.setRegexp((String) plh, asString(val, null)); + } + break; + case 6: // JSON + if (plh instanceof Number) { + q.setJSON(((Number) plh).intValue(), asString(val, null)); + } else { + q.setJSON((String) plh, asString(val, null)); + } + break; + } + } + } + + qh.handle(q, hook); + + } finally { + q.close(); + } + } + + private static void onlineBackup(DbMethodCall mc) { + String targetFile = asString(mc.args[1], null); + Log.w(TAG, "Online backup into: " + targetFile); + mc.successOnMainThread(mc.getDb().onlineBackup(targetFile)); + } + + private static void removeFloatIndex(DbMethodCall mc) { + String coll = asString(mc.args[1], null); + String path = asString(mc.args[2], null); + boolean unique = asBoolean(mc.args[3], false); + mc.getDb().removeFloatIndex(coll, path, unique); + mc.successOnMainThread(null); + } + + private static void ensureFloatIndex(DbMethodCall mc) { + String coll = asString(mc.args[1], null); + String path = asString(mc.args[2], null); + boolean unique = asBoolean(mc.args[3], false); + mc.getDb().ensureFloatIndex(coll, path, unique); + mc.successOnMainThread(null); + } + + private static void removeIntIndex(DbMethodCall mc) { + String coll = asString(mc.args[1], null); + String path = asString(mc.args[2], null); + boolean unique = asBoolean(mc.args[3], false); + mc.getDb().removeIntIndex(coll, path, unique); + mc.successOnMainThread(null); + } + + private static void ensureIntIndex(DbMethodCall mc) { + String coll = asString(mc.args[1], null); + String path = asString(mc.args[2], null); + boolean unique = asBoolean(mc.args[3], false); + mc.getDb().ensureIntIndex(coll, path, unique); + mc.successOnMainThread(null); + } + + private static void removeStringIndex(DbMethodCall mc) { + String coll = asString(mc.args[1], null); + String path = asString(mc.args[2], null); + boolean unique = asBoolean(mc.args[3], false); + mc.getDb().removeStringIndex(coll, path, unique); + mc.successOnMainThread(null); + } + + private static void ensureStringIndex(DbMethodCall mc) { + String coll = asString(mc.args[1], null); + String path = asString(mc.args[2], null); + boolean unique = asBoolean(mc.args[3], false); + mc.getDb().ensureStringIndex(coll, path, unique); + mc.successOnMainThread(null); + } + + private static void removeCollection(DbMethodCall mc) { + String coll = asString(mc.args[1], null); + mc.getDb().removeCollection(coll); + mc.successOnMainThread(null); + } + + private static void renameCollection(DbMethodCall mc) { + String oldName = asString(mc.args[1], null); + String newName = asString(mc.args[2], null); + mc.getDb().renameCollection(oldName, newName); + mc.successOnMainThread(null); + } + + private static void del(DbMethodCall mc) { + String coll = asString(mc.args[1], null); + long id = asLong(mc.args[2], 0); + mc.getDb().del(coll, id); + mc.successOnMainThread(null); + } + + private static void get(DbMethodCall mc) throws Exception { + String coll = asString(mc.args[1], null); + long id = asLong(mc.args[2], 0); + mc.successOnMainThread(mc.getDb().getAsString(coll, id)); + } + + private static void patch(DbMethodCall mc) { + String coll = asString(mc.args[1], null); + String json = asString(mc.args[2], null); + long id = asLong(mc.args[3], 0); + boolean upsert = asBoolean(mc.args[4], false); + if (upsert) { + mc.getDb().patchOrPut(coll, json, id); + } else { + mc.getDb().patch(coll, json, id); + } + mc.successOnMainThread(null); + } + + private static void put(DbMethodCall mc) { + String coll = asString(mc.args[1], null); + String json = asString(mc.args[2], null); + long id = asLong(mc.args[3], 0); + mc.successOnMainThread(mc.getDb().put(coll, json, id)); + } + + private static void info(DbMethodCall mc) throws Exception { + mc.successOnMainThread(mc.dbe.db.infoAsString()); + } + + private static void open(DbMethodCall mc) { + synchronized (mc.plugin) { + String path = normalizePath(mc.getAppContext(), (String) mc.args[1]); + DbEntry dbe = null; + for (DbEntry v : dbmap.values()) { + if (v.path.equals(path)) { + dbe = v; + break; + } + } + if (dbe != null) { + dbe.countOpen(); + mc.successOnMainThread(dbe.handle); + return; + } + Map opts = cast(mc.args[2]); + EJDB2Builder b = new EJDB2Builder(path); + if (asBoolean(opts.get("readonly"), false)) { + b.readonly(); + } + if (asBoolean(opts.get("truncate"), false)) { + b.truncate(); + } + if (asBoolean(opts.get("wal_enabled"), true)) { + b.withWAL(); + } + b.walCRCOnCheckpoint(asBoolean(opts.get("wal_check_crc_on_checkpoint"), false)); + if (opts.containsKey("wal_checkpoint_buffer_sz")) { + b.walCheckpointBufferSize(asInt(opts.get("wal_checkpoint_buffer_sz"), 0)); + } + if (opts.containsKey("wal_checkpoint_timeout_sec")) { + b.walCheckpointTimeoutSec(asInt(opts.get("wal_checkpoint_timeout_sec"), 0)); + } + if (opts.containsKey("wal_savepoint_timeout_sec")) { + b.walSavepointTimeoutSec(asInt(opts.get("wal_savepoint_timeout_sec"), 0)); + } + if (opts.containsKey("wal_wal_buffer_sz")) { + b.walBufferSize(asInt(opts.get("wal_wal_buffer_sz"), 0)); + } + if (opts.containsKey("document_buffer_sz")) { + b.documentBufferSize(asInt(opts.get("document_buffer_sz"), 0)); + } + if (opts.containsKey("sort_buffer_sz")) { + b.sortBufferSize(asInt(opts.get("sort_buffer_sz"), 0)); + } + + final Integer handle = dbkeys.incrementAndGet(); + dbmap.put(handle, new DbEntry(b.open(), handle, path)); + mc.successOnMainThread(handle); + } + } + + private static void close(DbMethodCall mc) { + synchronized (mc.plugin) { + if (mc.dbe.close()) { + dbmap.remove(mc.dbe.handle); + } + } + mc.successOnMainThread(null); + } + + @SuppressWarnings("unchecked") + private static T cast(Object obj) { + return (T) obj; + } + + private static boolean asBoolean(Object obj, boolean dv) { + if (obj instanceof Boolean) { + return ((Boolean) obj).booleanValue(); + } else { + return obj != null ? Boolean.parseBoolean(obj.toString()) : dv; + } + } + + private static int asInt(Object obj, int dv) { + if (obj instanceof Number) { + return ((Number) obj).intValue(); + } else { + return obj != null ? Integer.parseInt(obj.toString()) : dv; + } + } + + private static long asLong(Object obj, long dv) { + if (obj instanceof Number) { + return ((Number) obj).longValue(); + } else { + return obj != null ? Long.parseLong(obj.toString()) : dv; + } + } + + private static double asDouble(Object obj, double dv) { + if (obj instanceof Number) { + return ((Number) obj).doubleValue(); + } else { + return obj != null ? Double.parseDouble(obj.toString()) : dv; + } + } + + private static String asString(Object obj, String dv) { + return obj != null ? obj.toString() : dv; + } + + private static String normalizePath(Context ctx, String path) { + return ctx.getDatabasePath(path).getAbsolutePath(); + } + + private static DbMethod thread(DbMethod dbm) { + return mc -> executor.execute(() -> { + try { + dbm.handle(mc); + } catch (EJDB2Exception e) { + mc.errorOnMainThread("@ejdb IWRC:" + e.getCode(), e.getMessage(), null); + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + mc.errorOnMainThread("@ejdb", e.toString(), null); + } + }); + } + + private static final class DbMethodCall { + private DbMethodCall(Ejdb2FlutterPlugin plugin, DbEntry dbe, Object[] args, Result result) { + this.plugin = plugin; + this.dbe = dbe; + this.args = args; + this.result = result; + } + + private final Ejdb2FlutterPlugin plugin; + private final DbEntry dbe; + private final Object[] args; + private final Result result; + + private EJDB2 getDb() { + return dbe.db; + } + + private Context getAppContext() { + return plugin.registrar.context(); + } + + private Activity getActivity() { + return plugin.registrar.activity(); + } + + private boolean runOnMainThread(Runnable action) { + Activity activity = getActivity(); + if (activity != null) { + activity.runOnUiThread(action); + return true; + } else { + action.run(); + } + return false; + } + + private void successOnMainThread(Object val) { + runOnMainThread(() -> result.success(val)); + } + + private void errorOnMainThread(String errorCode, String errorMessage, Object errorDetails) { + runOnMainThread(() -> result.error(errorCode, errorMessage, errorDetails)); + } + } + + private static final class DbEntry { + DbEntry(EJDB2 db, Integer handle, String path) { + this.db = db; + this.counter = new AtomicInteger(1); + this.path = path; + this.handle = handle; + } + + private final EJDB2 db; + private final AtomicInteger counter; + private final String path; + private final Integer handle; + + void countOpen() { + this.counter.incrementAndGet(); + } + + boolean close() { + int cnt = this.counter.decrementAndGet(); + if (cnt <= 0) { + db.close(); + } + return cnt <= 0; + } + } + + private interface DbMethod { + void handle(DbMethodCall mc) throws Exception; + } + + private interface DbQueryHandler { + void handle(JQL q, String hook) throws Exception; + } +} diff --git a/src/bindings/ejdb2_flutter/example/.gitignore b/src/bindings/ejdb2_flutter/example/.gitignore new file mode 100644 index 0000000..9a228e4 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/.gitignore @@ -0,0 +1,75 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.packages +.pub-cache/ +.pub/ +/build/ + +# Android related +# **/android/**/gradle-wrapper.jar +# **/android/gradlew.bat +# **/android/gradlew +**/android/.gradle +**/android/captures/ +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages + +!*.iml diff --git a/src/bindings/ejdb2_flutter/example/.metadata b/src/bindings/ejdb2_flutter/example/.metadata new file mode 100644 index 0000000..fea404f --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 68587a0916366e9512a78df22c44163d041dd5f3 + channel: stable + +project_type: app diff --git a/src/bindings/ejdb2_flutter/example/README.md b/src/bindings/ejdb2_flutter/example/README.md new file mode 100644 index 0000000..0fa6d7e --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/README.md @@ -0,0 +1,16 @@ +# ejdb2_example + +Demonstrates how to use the ejdb2 plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/src/bindings/ejdb2_flutter/example/android/.project b/src/bindings/ejdb2_flutter/example/android/.project new file mode 100644 index 0000000..3964dd3 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/android/.project @@ -0,0 +1,17 @@ + + + android + Project android created by Buildship. + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/src/bindings/ejdb2_flutter/example/android/app/.project b/src/bindings/ejdb2_flutter/example/android/app/.project new file mode 100644 index 0000000..ac485d7 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/android/app/.project @@ -0,0 +1,23 @@ + + + app + Project app created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/src/bindings/ejdb2_flutter/example/android/app/build.gradle b/src/bindings/ejdb2_flutter/example/android/app/build.gradle new file mode 100644 index 0000000..1e6090c --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/android/app/build.gradle @@ -0,0 +1,61 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.softmotions.ejdb2_example" + minSdkVersion 16 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' +} diff --git a/src/bindings/ejdb2_flutter/example/android/app/src/debug/AndroidManifest.xml b/src/bindings/ejdb2_flutter/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..3bc393b --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/src/bindings/ejdb2_flutter/example/android/app/src/main/AndroidManifest.xml b/src/bindings/ejdb2_flutter/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c085d1e --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/src/bindings/ejdb2_flutter/example/android/app/src/main/java/com/softmotions/ejdb2_example/MainActivity.java b/src/bindings/ejdb2_flutter/example/android/app/src/main/java/com/softmotions/ejdb2_example/MainActivity.java new file mode 100644 index 0000000..c31163e --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/android/app/src/main/java/com/softmotions/ejdb2_example/MainActivity.java @@ -0,0 +1,13 @@ +package com.softmotions.ejdb2_example; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class MainActivity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/src/bindings/ejdb2_flutter/example/android/app/src/main/res/drawable/launch_background.xml b/src/bindings/ejdb2_flutter/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/bindings/ejdb2_flutter/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/src/bindings/ejdb2_flutter/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/bindings/ejdb2_flutter/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/src/bindings/ejdb2_flutter/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/bindings/ejdb2_flutter/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/src/bindings/ejdb2_flutter/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/bindings/ejdb2_flutter/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/src/bindings/ejdb2_flutter/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/bindings/ejdb2_flutter/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/src/bindings/ejdb2_flutter/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/bindings/ejdb2_flutter/example/android/app/src/main/res/values/styles.xml b/src/bindings/ejdb2_flutter/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..00fa441 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/src/bindings/ejdb2_flutter/example/android/app/src/profile/AndroidManifest.xml b/src/bindings/ejdb2_flutter/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..3bc393b --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/src/bindings/ejdb2_flutter/example/android/build.gradle b/src/bindings/ejdb2_flutter/example/android/build.gradle new file mode 100644 index 0000000..951a029 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/android/build.gradle @@ -0,0 +1,25 @@ +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + } +} +allprojects { + repositories { + google() + jcenter() + } +} +rootProject.buildDir = '../build' + +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/src/bindings/ejdb2_flutter/example/android/gradle.properties b/src/bindings/ejdb2_flutter/example/android/gradle.properties new file mode 100644 index 0000000..e30ec9d --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1024M +android.useAndroidX=true +android.enableJetifier=true diff --git a/src/bindings/ejdb2_flutter/example/android/gradle/wrapper/gradle-wrapper.jar b/src/bindings/ejdb2_flutter/example/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5c2d1cf Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src/bindings/ejdb2_flutter/example/android/gradle/wrapper/gradle-wrapper.properties b/src/bindings/ejdb2_flutter/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..f4d7b2b --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/src/bindings/ejdb2_flutter/example/android/gradlew b/src/bindings/ejdb2_flutter/example/android/gradlew new file mode 100755 index 0000000..9d82f78 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/android/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/src/bindings/ejdb2_flutter/example/android/gradlew.bat b/src/bindings/ejdb2_flutter/example/android/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/bindings/ejdb2_flutter/example/android/settings.gradle b/src/bindings/ejdb2_flutter/example/android/settings.gradle new file mode 100644 index 0000000..5a2f14f --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/src/bindings/ejdb2_flutter/example/ios/.gitignore b/src/bindings/ejdb2_flutter/example/ios/.gitignore new file mode 100644 index 0000000..e96ef60 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/src/bindings/ejdb2_flutter/example/ios/Flutter/AppFrameworkInfo.plist b/src/bindings/ejdb2_flutter/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..6b4c0f7 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/src/bindings/ejdb2_flutter/example/ios/Flutter/Debug.xcconfig b/src/bindings/ejdb2_flutter/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..e8efba1 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/src/bindings/ejdb2_flutter/example/ios/Flutter/Release.xcconfig b/src/bindings/ejdb2_flutter/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..399e934 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/src/bindings/ejdb2_flutter/example/ios/Podfile b/src/bindings/ejdb2_flutter/example/ios/Podfile new file mode 100644 index 0000000..865facc --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Podfile @@ -0,0 +1,95 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + generated_key_values = {} + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values +end + +target 'Runner' do + use_frameworks! + use_modular_headers! + + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end +end + +# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. +install! 'cocoapods', :disable_input_output_paths => true + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end + +pre_install do |installer| + # workaround for https://github.com/CocoaPods/CocoaPods/issues/3289 + Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {} +end diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/src/bindings/ejdb2_flutter/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..7bd20f2 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,583 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + EE41B942CB0C440CF20DD584 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F64F9FEEE6C98D20E00B4214 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + BCAD53272E9DBD49C6B92F9E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + C31F2AB4B28DA314400465C8 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + DCA5BC6A1307BEDBD6EAC2DF /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + F64F9FEEE6C98D20E00B4214 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + EE41B942CB0C440CF20DD584 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4EE010516AD2460B869FCCF6 /* Pods */ = { + isa = PBXGroup; + children = ( + BCAD53272E9DBD49C6B92F9E /* Pods-Runner.debug.xcconfig */, + C31F2AB4B28DA314400465C8 /* Pods-Runner.release.xcconfig */, + DCA5BC6A1307BEDBD6EAC2DF /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 81B1AFBC58554C61396BB482 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F64F9FEEE6C98D20E00B4214 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B80C3931E831B6300D905FE /* App.framework */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEBA1CF902C7004384FC /* Flutter.framework */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 4EE010516AD2460B869FCCF6 /* Pods */, + 81B1AFBC58554C61396BB482 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + BA50F92EBCFF5C49767E4265 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 780C9855C7DBEFE54AE102C2 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + }; + 780C9855C7DBEFE54AE102C2 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + BA50F92EBCFF5C49767E4265 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.softmotions.ejdb2.ejdb2FlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.softmotions.ejdb2.ejdb2FlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.softmotions.ejdb2.ejdb2FlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/src/bindings/ejdb2_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/src/bindings/ejdb2_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..a28140c --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/src/bindings/ejdb2_flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/src/bindings/ejdb2_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/AppDelegate.swift b/src/bindings/ejdb2_flutter/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/src/bindings/ejdb2_flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Base.lproj/Main.storyboard b/src/bindings/ejdb2_flutter/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..56b09e9 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Info.plist b/src/bindings/ejdb2_flutter/example/ios/Runner/Info.plist new file mode 100644 index 0000000..89d05b9 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ejdb2_flutter_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/src/bindings/ejdb2_flutter/example/ios/Runner/Runner-Bridging-Header.h b/src/bindings/ejdb2_flutter/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..7335fdf --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" \ No newline at end of file diff --git a/src/bindings/ejdb2_flutter/example/lib/main.dart b/src/bindings/ejdb2_flutter/example/lib/main.dart new file mode 100644 index 0000000..f790838 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/lib/main.dart @@ -0,0 +1,224 @@ +import 'dart:async'; + +import 'package:json_at/json_at.dart'; +import 'package:ejdb2_example/utils/assertions.dart'; +import 'package:ejdb2_flutter/ejdb2_flutter.dart'; +import 'package:flutter/material.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State { + String _result = ''; + + @override + void initState() { + super.initState(); + _initTesting(); + } + + Future _doTest() async { + String dbpath; + + var db = await (EJDB2Builder('test.db') + ..truncate = true + ..walEnabled = true) + .open(); + try { + var id = await db.put('mycoll', {'foo': 'bar'}); + assertEqual(id, 1); + + JBDOC doc = await db.get('mycoll', id); + assertDeepEqual(doc.object, {'foo': 'bar'}); + + id = await db.put('mycoll', {'foo': 'baz'}, id); + assertEqual(id, 1); + + doc = await db.get('mycoll', id); + assertDeepEqual(doc.object, {'foo': 'baz'}); + + dynamic error; + try { + await db.put('mycoll', '{"'); + } catch (e) { + error = e; + } + assertNotNull(error); + assertTrue(error is EJDB2Error); + assertEqual(error.code, '@ejdb IWRC:86005'); + + assertTrue('${error.message}'.contains('Unquoted JSON string (JBL_ERROR_PARSE_UNQUOTED_STRING)')); + + id = await db.put('mycoll', {'foo': 'bar'}); + assertEqual(id, 2); + + var list = await db.createQuery('@mycoll/*').list(); + assertEqual(list.toString(), '[JBDOC: 2 {"foo":"bar"}, JBDOC: 1 {"foo":"baz"}]'); + + JBDOC first = await db.createQuery('@mycoll/*').execute().first; + assertEqual('$first', 'JBDOC: 2 {"foo":"bar"}'); + + first = (await db.createQuery('@mycoll/*').first()).orNull; + assertEqual('$first', 'JBDOC: 2 {"foo":"bar"}'); + + first = (await db.createQuery('@mycoll/[zzz=bbb]').first()).orNull; + assertNull(first); + + list = await db.createQuery('@mycoll/*').firstN(5); + assertEqual('$list', '[JBDOC: 2 {"foo":"bar"}, JBDOC: 1 {"foo":"baz"}]'); + + doc = await db + .createQuery('@mycoll/[foo=:? and foo=:bar]') + .setString(0, 'baz') + .setString('bar', 'baz') + .execute() + .first; + assertEqual('$doc', 'JBDOC: 1 {"foo":"baz"}'); + + list = await (db.createQuery('@mycoll/[foo != :?]').setString(0, 'zaz')..skip = 1) + .execute() + .toList(); + assertEqual('$list', '[JBDOC: 1 {"foo":"baz"}]'); + + error = null; + try { + await db['@mycoll/['].execute(); + } catch (e) { + error = e; + } + assertNull(error); // Note: null error + + error = null; + try { + await db.createQuery('@mycoll/[').executeTouch(); + } catch (e) { + error = e; + } + assertNotNull(error); // Note: non null error + assertTrue(error is EJDB2Error); + assertTrue((error as EJDB2Error).isInvalidQuery()); + + final count = await db['@mycoll/* | count'].executeScalarInt(); + assertEqual(count, 2); + + await db.patch('mycoll', '[{"op":"add", "path":"/baz", "value":"qux"}]', 1); + doc = await db.get('mycoll', 1); + assertDeepEqual(doc.object, {'foo': 'baz', 'baz': 'qux'}); + + var info = await db.info(); + assertEqual(jsonAt(info, '/size').orNull, 8192); + assertEqual(jsonAt(info, '/collections/0/rnum').orNull, 2); + assertNull(jsonAt(info, '/collections/0/indexes/0').orNull); + + await db.ensureStringIndex('mycoll', '/foo', true); + info = await db.info(); + assertEqual(jsonAt(info, '/collections/0/indexes/0/ptr').orNull, '/foo'); + assertEqual(jsonAt(info, '/collections/0/indexes/0/mode').orNull, 5); + assertEqual(jsonAt(info, '/collections/0/indexes/0/rnum').orNull, 2); + + await db.removeStringIndex('mycoll', '/foo', true); + info = await db.info(); + assertNull(jsonAt(info, '/collections/0/indexes/0').orNull); + + doc = await db['@mycoll/[foo re :?]'].setRegexp(0, RegExp('.*')).firstRequired(); + assertEqual('$doc', 'JBDOC: 2 {"foo":"bar"}'); + + await db.removeCollection('mycoll'); + info = await db.info(); + assertNull(jsonAt(info, '/collections/0').orNull); + + error = null; + try { + await db['@mycoll/*'].firstRequired(); + } catch (e) { + error = e; + } + assertNotNull(error); // Note: non null error + assertTrue(error is EJDB2Error); + assertTrue((error as EJDB2Error).isNotFound()); + + id = await db.put('cc1', {'foo': 1}); + doc = await db.get('cc1', id); + assertEqual('$doc', 'JBDOC: 1 {"foo":1}'); + + await db.renameCollection('cc1', 'cc2'); + doc = await db.get('cc2', id); + assertEqual('$doc', 'JBDOC: 1 {"foo":1}'); + + var opt = await db.getOptional('cc2', id); + assertTrue(opt.isPresent); + + opt = await db.getOptional('cc2', 122); + assertTrue(opt.isNotPresent); + + + var i = 0; + for (i = 0; i < 1023; ++i) { + await db.put('load', {'name': 'v${i}'}); + } + + i = 0; + await for (final doc in db.createQuery('@load/* | inverse').execute()) { + final v = doc.object; + assertEqual(v['name'], 'v${i}'); + ++i; + } + + dbpath = jsonAt(info, '/file').orNull as String; + assertNotNull(dbpath); + + final ts0 = DateTime.now().millisecondsSinceEpoch; + final ts = await db.onlineBackup('${dbpath}.bkp'); + assertTrue(ts0 < ts); + + } catch(e, st) { + print('$e\n$st'); + rethrow; + } finally { + await db.close(); + } + + db = await EJDB2Builder('${dbpath}.bkp').open(); + final doc = await db.get('cc2', 1); + assertEqual('${doc}', 'JBDOC: 1 {"foo":1}'); + await db.close(); + } + + Future _initTesting() async { + if (!mounted) return; + try { + await _doTest(); + setState(() { + _result = 'Success'; + }); + } catch (e, s) { + print('Error $e, $s'); + setState(() { + _result = 'Fail: ${e}'; + }); + } + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: _result == 'Success' + ? const Text( + 'Success', + key: Key('success'), + ) + : Text( + _result == '' ? '' : 'Fail: ${_result}', + key: const Key('fail'), + ), + ), + ), + ); + } +} diff --git a/src/bindings/ejdb2_flutter/example/lib/utils/assertions.dart b/src/bindings/ejdb2_flutter/example/lib/utils/assertions.dart new file mode 100644 index 0000000..6cd34c4 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/lib/utils/assertions.dart @@ -0,0 +1,81 @@ +import 'package:collection/collection.dart'; + +class AssertionException implements Exception { + AssertionException(this.check, [this.message]); + + final String check; + final String message; + + @override + String toString() { + if (message == null) return 'AssertionException.${check}'; + return 'AssertionException.${check}: $message'; + } +} + +void assertTrue(bool val, [String message]) { + if (!val) { + throw AssertionException('assertTrue', message); + } +} + +void assertFalse(bool val, [String message]) { + if (val) { + throw AssertionException('assertFalse', message); + } +} + +void assertEqual(var v1, var v2, [String message]) { + if (v1 != v2) { + throw AssertionException('assertEqual', message); + } +} + +void assertIdentical(var v1, var v2, [String message]) { + if (!identical(v1, v2)) { + throw AssertionException('assertIdentical', message); + } +} + +void assertNotIdentical(var v1, var v2, [String message]) { + if (identical(v1, v2)) { + throw AssertionException('assertNotIdentical', message); + } +} + +void assertDeepEqual(var v1, var v2, [bool unordered = true, String message]) { + final deepEq = unordered + ? const DeepCollectionEquality.unordered().equals + : const DeepCollectionEquality().equals; + if (!deepEq(v1, v2)) { + throw AssertionException('assertDeepEquals', message); + } +} + +void assertDeepNotEqual(var v1, var v2, [bool unordered = true, String message]) { + final deepEq = unordered + ? const DeepCollectionEquality.unordered().equals + : const DeepCollectionEquality().equals; + if (deepEq(v1, v2)) { + throw AssertionException('assertNotDeepEquals', message); + } +} + +void assertNotEqual(var v1, var v2, [String message]) { + if (v1 == v2) { + throw AssertionException('assertNotEquals', message); + } +} + +void assertNotNull(var v1, [String message]) { + if (v1 == null) { + throw AssertionException('assertNotNull', message); + } +} + +void assertNull(var v1, [String message]) { + if (v1 != null) { + throw AssertionException('assertNull', message); + } +} + diff --git a/src/bindings/ejdb2_flutter/example/pubspec.yaml b/src/bindings/ejdb2_flutter/example/pubspec.yaml new file mode 100644 index 0000000..4e5de8d --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/pubspec.yaml @@ -0,0 +1,64 @@ +name: ejdb2_example +description: Demonstrates how to use the ejdb2 plugin. +publish_to: 'none' + +environment: + sdk: ">=2.1.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^0.1.2 + +dev_dependencies: + flutter_driver: + sdk: flutter + test: any + + ejdb2_flutter: + path: ../ + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/src/bindings/ejdb2_flutter/example/test_driver/app.dart b/src/bindings/ejdb2_flutter/example/test_driver/app.dart new file mode 100644 index 0000000..e29b5bd --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/test_driver/app.dart @@ -0,0 +1,7 @@ +import 'package:flutter_driver/driver_extension.dart'; +import 'package:ejdb2_example/main.dart' as app; + +void main() { + enableFlutterDriverExtension(); + app.main(); +} diff --git a/src/bindings/ejdb2_flutter/example/test_driver/app_test.dart b/src/bindings/ejdb2_flutter/example/test_driver/app_test.dart new file mode 100644 index 0000000..81c1470 --- /dev/null +++ b/src/bindings/ejdb2_flutter/example/test_driver/app_test.dart @@ -0,0 +1,22 @@ +import 'package:flutter_driver/flutter_driver.dart'; +import 'package:test/test.dart'; + +void main() { + group('EJDB2 tests', () { + FlutterDriver driver; + + setUpAll(() async { + driver = await FlutterDriver.connect(); + }); + + tearDownAll(() async { + if (driver != null) { + await driver.close(); + } + }); + + test('main test', () async { + await driver.waitFor(find.byValueKey('success'), timeout: const Duration(seconds: 10)); + }); + }); +} diff --git a/src/bindings/ejdb2_flutter/ios/.gitignore b/src/bindings/ejdb2_flutter/ios/.gitignore new file mode 100644 index 0000000..aa479fd --- /dev/null +++ b/src/bindings/ejdb2_flutter/ios/.gitignore @@ -0,0 +1,37 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/src/bindings/ejdb2_flutter/ios/Assets/.gitkeep b/src/bindings/ejdb2_flutter/ios/Assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/bindings/ejdb2_flutter/ios/Classes/Ejdb2FlutterPlugin.h b/src/bindings/ejdb2_flutter/ios/Classes/Ejdb2FlutterPlugin.h new file mode 100644 index 0000000..a2ab08d --- /dev/null +++ b/src/bindings/ejdb2_flutter/ios/Classes/Ejdb2FlutterPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface Ejdb2FlutterPlugin : NSObject +@end diff --git a/src/bindings/ejdb2_flutter/ios/Classes/Ejdb2FlutterPlugin.m b/src/bindings/ejdb2_flutter/ios/Classes/Ejdb2FlutterPlugin.m new file mode 100644 index 0000000..a003fdc --- /dev/null +++ b/src/bindings/ejdb2_flutter/ios/Classes/Ejdb2FlutterPlugin.m @@ -0,0 +1,15 @@ +#import "Ejdb2FlutterPlugin.h" +#if __has_include() +#import +#else +// Support project import fallback if the generated compatibility header +// is not copied when this plugin is created as a library. +// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 +#import "ejdb2_flutter-Swift.h" +#endif + +@implementation Ejdb2FlutterPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [SwiftEjdb2FlutterPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/src/bindings/ejdb2_flutter/ios/Classes/SwiftEjdb2FlutterPlugin.swift b/src/bindings/ejdb2_flutter/ios/Classes/SwiftEjdb2FlutterPlugin.swift new file mode 100644 index 0000000..c1e2008 --- /dev/null +++ b/src/bindings/ejdb2_flutter/ios/Classes/SwiftEjdb2FlutterPlugin.swift @@ -0,0 +1,538 @@ +import Flutter +import EJDB2 +import PathKit + +typealias MethodInvocation = (_: DbEntry) -> Void + +typealias DbQueryHandler = (_: SWJQL, _: String?) throws -> Void + +extension Dictionary { + func containsKey(_ key: Key) -> Bool { + self[key] != nil + } +} + +func asBoolean(_ v: Any?, _ def: Bool = false) -> Bool { + if let c = v as? NSNumber { + return c.intValue > 0 + } + if let c = v as? NSString { + return c.isEqual(to: "true") + } + if let c = v as? Bool { + return c + } + return def +} + +func asNumber(_ v: Any?) -> NSNumber? { + if let c = v as? NSNumber { + return c + } + if let c = v as? String { + return NSNumber(value: Int64(c) ?? 0) + } + return nil +} + +func asDouble(_ v: Any?) -> Double? { + if let c = v as? NSNumber { + return c.doubleValue + } + if let c = v as? String { + return Double(c) + } + return nil +} + +func asString(_ v: Any?, _ def: String? = nil) -> String? { + if let c = v as? String { + return c + } + if v != nil { + return String(describing: v!) + } + return def +} + +class DbEntry { + let db: EJDB2DB + let handle: UInt32 + let path: String + var counter: Int = 0 + + init(_ db: EJDB2DB, _ handle: UInt32, _ path: String) { + self.db = db + self.handle = handle + self.path = path + } + + func countOpen() { + counter += 1 + } + + func close() throws -> Bool { + counter -= 1 + if counter <= 0 { + try db.close() + } + return counter <= 0 + } +} + +struct DbMethodCall { + let dbe: DbEntry? + let args: [Any?] + let result: FlutterResult + + var db: EJDB2DB { + dbe!.db + } + + func successOnMainThread(_ val: Any? = nil) { + DispatchQueue.main.async { + self.result(val) + } + } + + func errorOnMainThread(_ code: String, _ message: String?, _ details: Any? = nil) { + DispatchQueue.main.async { + self.result(FlutterError(code: code, message: message, details: details)) + } + } +} + +public class SwiftEjdb2FlutterPlugin: NSObject, FlutterPlugin, FlutterStreamHandler { + + public static func register(with registrar: FlutterPluginRegistrar) { + let _ = SwiftEjdb2FlutterPlugin(registrar) + } + + let methodChannel: FlutterMethodChannel + let eventChannel: FlutterEventChannel + var dbmap: [UInt32: DbEntry] = [:] + var dbkeys: UInt32 = 0 + var events: FlutterEventSink? + + init(_ registrar: FlutterPluginRegistrar) { + self.methodChannel = FlutterMethodChannel(name: "ejdb2", binaryMessenger: registrar.messenger()) + self.eventChannel = FlutterEventChannel(name: "ejdb2/query", binaryMessenger: registrar.messenger()) + super.init() + registrar.addMethodCallDelegate(self, channel: methodChannel) + eventChannel.setStreamHandler(self) + } + + func execute(_ mc: DbMethodCall, _ block: @escaping (_: DbMethodCall) throws -> Void) { + DispatchQueue.global().async { + do { + try block(mc) + } catch let error as EJDB2Error { + mc.errorOnMainThread("@ejdb IWRC:\(error.code)", error.message) + } catch { + mc.errorOnMainThread("@ejdb", "\(error)") + } + } + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + // NSLog("\nCall method \(call.method)") + let args = call.arguments as? [Any?] ?? [] + var dbe: DbEntry? + if let dbid = args.first as? NSNumber { + dbe = dbmap[dbid.uint32Value] + if dbe == nil { + result(FlutterError(code: "@ejdb", message: "Database handle already disposed", details: nil)) + return + } + } + + let mc = DbMethodCall(dbe: dbe, args: args, result: result) + + switch call.method { + case "get": + execute(mc, get) + break + case "put": + execute(mc, put) + break; + case "patch": + execute(mc, patch) + break + case "del": + execute(mc, del) + break + case "info": + execute(mc, info) + break + case "executeList": + execute(mc, executeList) + break + case "executeFirst": + execute(mc, executeFirst) + break + case "executeScalarInt": + execute(mc, executeScalarInt) + break + case "executeQuery": + execute(mc, executeQuery) + break + case "renameCollection": + execute(mc, renameCollection) + break + case "removeCollection": + execute(mc, removeCollection) + break + case "ensureFloatIndex": + execute(mc, ensureFloatIndex) + break + case "ensureStringIndex": + execute(mc, ensureStringIndex) + break + case "ensureIntIndex": + execute(mc, ensureIntIndex) + break + case "removeFloatIndex": + execute(mc, removeFloatIndex) + break + case "removeStringIndex": + execute(mc, removeStringIndex) + break + case "removeIntIndex": + execute(mc, removeIntIndex) + break + case "onlineBackup": + execute(mc, onlineBackup) + break + case "open": + execute(mc, open) + break + case "close": + execute(mc, close) + break + default: + NSLog("\nMethod call: \(call.method) not implemented") + result(FlutterMethodNotImplemented) + break + } + } + + func executeFirst(_ mc: DbMethodCall) throws { + return try executeListImpl(mc, 1) + } + + func executeList(_ mc: DbMethodCall) throws { + return try executeListImpl(mc) + } + + func executeListImpl(_ mc: DbMethodCall, _ limit: Int64? = nil) throws { + try prepareQuery(mc) { q, _ in + if limit != nil { + q.setLimit(limit!) + } + var res: [Any?] = [] + try q.execute() { doc in + res.append(doc.id) + res.append(doc.json) + return true + } + mc.successOnMainThread(res) + } + } + + func executeScalarInt(_ mc: DbMethodCall) throws { + try prepareQuery(mc) { q, _ in + let res = try q.executeScalarInt() + mc.successOnMainThread(res) + } + } + + func executeQuery(_ mc: DbMethodCall) throws { + try prepareQuery(mc) { q, hook in + var batch: [Any?] = [] + try q.execute() { doc in + if batch.count >= 128 && batch.count % 2 == 0 { + batch.insert(hook, at: 0) + let res = batch + DispatchQueue.main.sync { + self.events?(res) + } + batch = [] + } + batch.append(doc.id) + batch.append(doc.json) + return true + } + batch.insert(hook, at: 0) + batch.append(true) + DispatchQueue.main.sync { + self.events?(batch) + } + } + } + + func prepareQuery(_ mc: DbMethodCall, _ qh: DbQueryHandler) throws { + let db = mc.db + let hook = asString(mc.args[1]) + let coll = asString(mc.args[2]) + let qtext = asString(mc.args[3], "")! + let qspec = mc.args[4] as! [String: Any?] + let params = mc.args[5] as! [[Any?]] + + let q = try db.createQuery(qtext, coll) + qspec.forEach { k, v in + switch (k) { + case "l": // limit + q.setLimit(asNumber(v)?.int64Value ?? 0) + break + case "s": // skip + q.setSkip(asNumber(v)?.int64Value ?? 0) + break + default: + break + } + } + for pslot in params { + let type = asNumber(pslot[0])?.intValue + let plh = pslot[1] + let val = pslot[2] + if type == 0 || val == nil { + if let v = plh as? NSNumber { + try q.setNull(v.int32Value) + } else { + try q.setNull(plh as! String) + } + } else { + switch (type) { + case 1: // String + if let v = plh as? NSNumber { + try q.setString(v.int32Value, asString(val)) + } else { + try q.setString(plh as! String, asString(val)) + } + break + case 2: // Long + if let v = plh as? NSNumber { + try q.setInt64(v.int32Value, asNumber(val)?.int64Value) + } else { + try q.setInt64(plh as! String, asNumber(val)?.int64Value) + } + break + case 3: // Double + if let v = plh as? NSNumber { + try q.setDouble(v.int32Value, asDouble(val)) + } else { + try q.setDouble(plh as! String, asDouble(val)) + } + break + case 4: // Boolean + if let v = plh as? NSNumber { + try q.setBool(v.int32Value, asBoolean(val)) + } else { + try q.setBool(plh as! String, asBoolean(val)) + } + break + case 5: // Regexp + if let v = plh as? NSNumber { + try q.setRegexp(v.int32Value, asString(val)) + } else { + try q.setRegexp(plh as! String, asString(val)) + } + break + case 6: // JSON + if let v = plh as? NSNumber { + try q.setJson(v.int32Value, val!) + } else { + try q.setJson(plh as! String, val!) + } + break + default: + break + } + } + } + try qh(q, hook) + } + + func onlineBackup(_ mc: DbMethodCall) throws { + var path = Path(asString(mc.args[1])!) + if !path.isAbsolute { + let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + path = Path(documentsPath) + path + } + NSLog("\nOnline backup into: \(path)") + mc.successOnMainThread(try mc.db.onlineBackup(path.string)) + } + + func removeFloatIndex(_ mc: DbMethodCall) throws { + let coll = asString(mc.args[1])! + let path = asString(mc.args[2])! + let unique = asBoolean(mc.args[3]) + try mc.db.removeFloatIndex(coll, path, unique: unique) + mc.successOnMainThread() + } + + func ensureFloatIndex(_ mc: DbMethodCall) throws { + let coll = asString(mc.args[1])! + let path = asString(mc.args[2])! + let unique = asBoolean(mc.args[3]) + try mc.db.ensureFloatIndex(coll, path, unique: unique) + mc.successOnMainThread() + } + + func removeIntIndex(_ mc: DbMethodCall) throws { + let coll = asString(mc.args[1])! + let path = asString(mc.args[2])! + let unique = asBoolean(mc.args[3]) + try mc.db.removeIntIndex(coll, path, unique: unique) + mc.successOnMainThread() + } + + func ensureIntIndex(_ mc: DbMethodCall) throws { + let coll = asString(mc.args[1])! + let path = asString(mc.args[2])! + let unique = asBoolean(mc.args[3]) + try mc.db.ensureIntIndex(coll, path, unique: unique) + mc.successOnMainThread() + } + + func removeStringIndex(_ mc: DbMethodCall) throws { + let coll = asString(mc.args[1])! + let path = asString(mc.args[2])! + let unique = asBoolean(mc.args[3]) + try mc.db.removeStringIndex(coll, path, unique: unique) + mc.successOnMainThread() + } + + func ensureStringIndex(_ mc: DbMethodCall) throws { + let coll = asString(mc.args[1])! + let path = asString(mc.args[2])! + let unique = asBoolean(mc.args[3]) + try mc.db.ensureStringIndex(coll, path, unique: unique) + mc.successOnMainThread() + } + + func removeCollection(_ mc: DbMethodCall) throws { + let coll = asString(mc.args[1])! + try mc.db.removeCollection(coll) + mc.successOnMainThread() + } + + func renameCollection(_ mc: DbMethodCall) throws { + let oldName = asString(mc.args[1])! + let newName = asString(mc.args[2])! + try mc.db.renameCollection(oldName, newName) + mc.successOnMainThread() + } + + func del(_ mc: DbMethodCall) throws { + let coll = asString(mc.args[1])! + let id = asNumber(mc.args[2])!.int64Value + try mc.db.del(coll, id) + mc.successOnMainThread() + } + + func patch(_ mc: DbMethodCall) throws { + let db = mc.db + let coll = asString(mc.args[1])! + let json = asString(mc.args[2])! + let id = asNumber(mc.args[3])!.int64Value + let upsert = asBoolean(mc.args[4]) + if upsert { + try db.put(coll, json, id, merge: true) + } else { + try db.patch(coll, json, id) + } + mc.successOnMainThread() + } + + func get(_ mc: DbMethodCall) throws { + let db = mc.db + let coll = asString(mc.args[1])! + let id = asNumber(mc.args[2])!.int64Value + let doc = try db.get(coll, id) + mc.successOnMainThread(doc.json) + } + + func put(_ mc: DbMethodCall) throws { + let db = mc.db + let coll = asString(mc.args[1])! + let json = asString(mc.args[2])! + let id = asNumber(mc.args[3])?.int64Value ?? 0 + let res = try db.put(coll, json, id) + mc.successOnMainThread(res) + } + + func info(_ mc: DbMethodCall) throws { + let info = try mc.db.info() + let data = String(data: try JSONEncoder().encode(info), encoding: .utf8) + mc.successOnMainThread(data) + } + + func open(_ mc: DbMethodCall) throws { + var path = Path(mc.args[1] as! String) + if !path.isAbsolute { + let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + path = Path(documentsPath) + path + } + path = path.normalize() + let dbe = dbmap.values.first(where: { $0.path == path.string }) + if dbe != nil { + dbe!.countOpen() + mc.successOnMainThread(dbe!.handle) + return + } + let cfg = mc.args[2] as? [String: Any] ?? [:] + var b = EJDB2Builder(path.string) + if cfg.containsKey("truncate") { + b = b.withTruncate(asBoolean(cfg["truncate"])) + } + if cfg.containsKey("readonly") { + b = b.withReadonly(asBoolean(cfg["readonly"])) + } + if cfg.containsKey("wal_enabled") { + b.withWalDisabled(!asBoolean(cfg["wal_enabled"], true)) + } + if cfg.containsKey("wal_check_crc_on_checkpoint") { + b.withWalCheckCRCOnCheckpoint(asBoolean(cfg["wal_check_crc_on_checkpoint"])) + } + if cfg.containsKey("wal_checkpoint_buffer_sz") { + b.withWalCheckpointBufferSize(asNumber(cfg["wal_checkpoint_buffer_sz"])?.uint64Value ?? 0) + } + if cfg.containsKey("wal_checkpoint_timeout_sec") { + b.withWalCheckpointTimeout(asNumber(cfg["wal_checkpoint_timeout_sec"])?.uint32Value ?? 0) + } + if cfg.containsKey("wal_savepoint_timeout_sec") { + b.withWalSavepointTimeout(asNumber(cfg["wal_checkpoint_timeout_sec"])?.uint32Value ?? 0) + } + if cfg.containsKey("wal_wal_buffer_sz") { + b.withWalBufferSize(asNumber(cfg["wal_wal_buffer_sz"])?.intValue ?? 0) + } + if cfg.containsKey("document_buffer_sz") { + b.withDocumentBufferSize(asNumber(cfg["document_buffer_sz"])?.uint32Value ?? 0) + } + if cfg.containsKey("sort_buffer_sz") { + b.withSortBufferSize(asNumber(cfg["sort_buffer_sz"])?.uint32Value ?? 0) + } + dbkeys += 1 + let key = dbkeys + dbmap[key] = try DbEntry(b.open(), key, path.string) + mc.successOnMainThread(key as NSNumber) + } + + func close(_ mc: DbMethodCall) throws { + if try mc.dbe?.close() ?? false { + dbmap.removeValue(forKey: mc.dbe?.handle ?? 0) + } + mc.successOnMainThread() + } + + public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { + self.events = events + return nil + } + + public func onCancel(withArguments arguments: Any?) -> FlutterError? { + self.events = nil + return nil + } +} diff --git a/src/bindings/ejdb2_flutter/ios/ejdb2_flutter.podspec b/src/bindings/ejdb2_flutter/ios/ejdb2_flutter.podspec new file mode 100644 index 0000000..e346503 --- /dev/null +++ b/src/bindings/ejdb2_flutter/ios/ejdb2_flutter.podspec @@ -0,0 +1,29 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint ejdb2_flutter.podspec' to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'ejdb2_flutter' + s.version = '1.0.10' + s.summary = 'Embeddable JSON Database engine EJDB http://ejdb.org Flutter binding' + s.description = <<-DESC +Embeddable JSON Database engine EJDB http://ejdb.org Flutter binding + DESC + s.homepage = 'https://ejdb.org' + s.license = { :file => '../LICENSE' } + s.author = { 'Softmotions Ltd.' => 'info@softmotions.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.static_framework = true + s.dependency 'Flutter' + s.dependency 'PathKit' + s.dependency 'EJDB2' + s.platform = :ios, '9.0' + + # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' + } + s.swift_version = '5.1' +end diff --git a/src/bindings/ejdb2_flutter/lib/ejdb2_flutter.dart b/src/bindings/ejdb2_flutter/lib/ejdb2_flutter.dart new file mode 100644 index 0000000..eb0277b --- /dev/null +++ b/src/bindings/ejdb2_flutter/lib/ejdb2_flutter.dart @@ -0,0 +1,460 @@ +// @dart=2.5 + +import 'dart:async'; +import 'dart:convert' as convert_lib; + +import 'package:flutter/services.dart'; +import 'package:json_at/json_at.dart'; +import 'package:pedantic/pedantic.dart'; +import 'package:quiver/core.dart'; + +const MethodChannel _mc = MethodChannel('ejdb2'); + +const EventChannel _qc = EventChannel('ejdb2/query'); + +Map> _ecm; + +void _execute(String hook, dynamic args, StreamController sctl) { + if (_ecm == null) { + _ecm = {}; + _qc.receiveBroadcastStream().listen(_onQueryData); + } + unawaited(_mc.invokeMethod('executeQuery', args).catchError((err, StackTrace s) { + _ecm.remove(hook); + dynamic streamError = err; + if (err is PlatformException && err.code.startsWith('@ejdb')) { + streamError = EJDB2Error(err.code, err.message); + } + sctl.addError(streamError, s); + })); + _ecm[hook] = sctl; +} + +void _onQueryData(dynamic dataArg) { + final data = dataArg as List; + if (data.isEmpty) { + return; + } + final qhook = data[0] as String; + final sctl = _ecm[qhook]; + if (sctl == null) { + return; + } + final last = data.last == true; + final l = last ? data.length - 1 : data.length; + for (int i = 1; i < l; i += 2) { + sctl.add(JBDOC(data[i] as int, data[i + 1] as String)); + } + if (last) { + _ecm.remove(qhook); + sctl.close(); + } +} + +/// EJDB2 Instance builder. +/// +class EJDB2Builder { + EJDB2Builder(this.path); + + final String path; + + bool readonly; + + bool truncate; + + bool walEnabled; + + bool walCheckCRCOnCheckpoint; + + int walCheckpointBufferSize; + + int walCheckpointTimeout; + + int walSavepointTimeout; + + int walBufferSize; + + int documentBufferSize; + + int sortBufferSize; + + /// Open EJDB2 database + /// See https://github.com/Softmotions/ejdb/blob/master/src/ejdb2.h#L104 + /// for description of options. + Future open() => EJDB2._open(this); + + Map _asOpts() => { + 'readonly': readonly ?? false, + 'truncate': truncate ?? false, + 'wal_enabled': walEnabled ?? true, + 'wal_check_crc_on_checkpoint': walCheckCRCOnCheckpoint ?? false, + ...walCheckpointBufferSize != null + ? {'wal_checkpoint_buffer_sz': walCheckpointBufferSize} + : {}, + ...walCheckpointTimeout != null ? {'wal_checkpoint_timeout_sec': walCheckpointTimeout} : {}, + ...walSavepointTimeout != null ? {'wal_savepoint_timeout_sec': walSavepointTimeout} : {}, + ...walBufferSize != null ? {'wal_wal_buffer_sz': walBufferSize} : {}, + ...documentBufferSize != null ? {'document_buffer_sz': documentBufferSize} : {}, + ...sortBufferSize != null ? {'sort_buffer_sz': sortBufferSize} : {} + }; +} + +FutureOr Function(Object, StackTrace) _errorHandler() => + (Object err, StackTrace s) { + if (err is PlatformException && err.code.startsWith('@ejdb')) { + return Future.error(EJDB2Error(err.code, err.message), s); + } + return Future.error(err, s); + }; + +class EJDB2Error implements Exception { + EJDB2Error(this.code, this.message); + EJDB2Error.notFound() : this('@ejdb IWRC:75001', 'Not found'); + + final String code; + final String message; + + bool isNotFound() => (code ?? '').startsWith('@ejdb IWRC:75001'); + + bool isInvalidQuery() => (code ?? '').startsWith('@ejdb IWRC:87001'); + + @override + String toString() => '$runtimeType: $code $message'; +} + +/// EJDB document item. +/// +class JBDOC { + JBDOC(this.id, this._json); + JBDOC._fromList(List list) : this(list[0] as int, list[1] as String); + + /// Document identifier + final int id; + + /// Document body as JSON string + String get json => _json ?? convert_lib.jsonEncode(_object); + + /// Document body as parsed JSON object. + dynamic get object { + if (_json == null) { + return _object; + } else { + _object = convert_lib.jsonDecode(_json); + _json = null; // Release memory used to store JSON string data + return _object; + } + } + + /// Gets subset of document using RFC 6901 JSON [pointer]. + Optional at(String pointer) => jsonAt(object, pointer); + + /// Gets subset of document using RFC 6901 JSON [pointer]. + Optional operator [](String pointer) => at(pointer); + + String _json; + + dynamic _object; + + @override + String toString() => '$runtimeType: $id $json'; +} + +/// EJDB2 query builder/executor. +/// +class JQL { + JQL._(this._jb, this.collection, this.qtext); + + static int _qhandle = 0; + + final EJDB2 _jb; + + final _qspec = {}; + + final _params = []; + + final String collection; + + final String qtext; + + int get limit => _qspec['l'] as int ?? 0; + + set limit(int limit) => _qspec['l'] = limit; + + int get skip => _qspec['s'] as int ?? 0; + + set skip(int skip) => _qspec['s'] = skip; + + int get _handle => _jb._handle; + + /// Set string [val] at the specified [placeholder]. + /// [placeholder] can be either `string` or `int` + JQL setString(dynamic placeholder, String val) { + _checkPlaceholder(placeholder); + ArgumentError.checkNotNull(val); + _params.add([1, placeholder, val]); + return this; + } + + /// Set integer [val] at the specified [placeholder]. + /// [placeholder] can be either `string` or `int` + JQL setInt(dynamic placeholder, int val) { + _checkPlaceholder(placeholder); + ArgumentError.checkNotNull(val); + _params.add([2, placeholder, val]); + return this; + } + + /// Set double [val] at the specified [placeholder]. + /// [placeholder] can be either `string` or `int` + JQL setDouble(dynamic placeholder, double val) { + _checkPlaceholder(placeholder); + ArgumentError.checkNotNull(val); + _params.add([3, placeholder, val]); + return this; + } + + /// Set bool [val] at the specified [placeholder]. + /// [placeholder] can be either `string` or `int` + JQL setBoolean(dynamic placeholder, bool val) { + _checkPlaceholder(placeholder); + ArgumentError.checkNotNull(val); + _params.add([4, placeholder, val]); + return this; + } + + /// Set RegExp [val] at the specified [placeholder]. + /// [placeholder] can be either `string` or `int` + JQL setRegexp(dynamic placeholder, RegExp val) { + _checkPlaceholder(placeholder); + ArgumentError.checkNotNull(val); + _params.add([5, placeholder, val.pattern]); + return this; + } + + /// Set [json] at the specified [placeholder]. + /// [placeholder] can be either `string` or `int` + JQL setJson(dynamic placeholder, Object json) { + _checkPlaceholder(placeholder); + ArgumentError.checkNotNull(json); + if (json is! String) { + json = convert_lib.jsonEncode(json); + } + _params.add([6, placeholder, json]); + return this; + } + + /// Execute query and returns a stream of matched documents. + Stream execute() { + final qh = '${++JQL._qhandle}'; + final sctl = StreamController(onCancel: () { + _ecm.remove(qh); + }); + final args = [_handle, qh, collection, qtext, _qspec, _params]; + _execute(qh, args, sctl); + return sctl.stream; + } + + /// Executes query then listen event stream for first event/error + /// to eagerly fetch pending error if available. + Future executeTouch() { + StreamSubscription subscription; + final Completer completer = Completer(); + final stream = execute(); + subscription = stream.listen((val) { + if (!completer.isCompleted) { + completer.complete(); + } + final sf = subscription.cancel(); + if (sf != null) { + unawaited(sf.catchError(() {})); + } + }, onError: (e, StackTrace s) { + if (!completer.isCompleted) { + completer.completeError(e, s); + } + }, onDone: () { + if (!completer.isCompleted) { + completer.complete(); + } + }, cancelOnError: true); + return completer.future; + } + + /// Return scalar integer value as result of query execution. + /// For example execution of count query: `/... | count` + Future executeScalarInt() => _mc.invokeMethod('executeScalarInt', + [_handle, null, collection, qtext, _qspec, _params]).then((v) => v as int); + + /// Returns list of matched documents. + /// Use it with care to avoid wasting of memory. + Future> list([int limitArg]) async { + var qspec = _qspec; + if (limitArg != null) { + // Copy on write to be safe + qspec = { + ..._qspec, + ...{'l': limitArg} + }; + } + final list = await _mc + .invokeListMethod('executeList', [_handle, null, collection, qtext, qspec, _params]); + final res = []; + for (int i = 0; i < list.length; i += 2) { + res.add(JBDOC(list[i] as int, list[i + 1] as String)); + } + return res; + } + + /// Returns optional element for first record in result set. + Future> first() async { + final l = await list(limit = 1); + return l.isNotEmpty ? Optional.of(l.first) : const Optional.absent(); + } + + /// Return first record in result set or resolve to notfound [EJDB2Error] error. + Future firstRequired() async { + final f = await first(); + if (f.isPresent) { + return f.value; + } + return Future.error(EJDB2Error.notFound()); + } + + /// Returns list of first [n] matched documents. + /// Use it with care to avoid wasting of memory. + Future> firstN(int n) => list(n); + + void _checkPlaceholder(dynamic placeholder) { + if (!(placeholder is String) && !(placeholder is int)) { + ArgumentError.value(placeholder, 'placeholder'); + } + } +} + +class EJDB2 { + EJDB2._(this._handle); + + final int _handle; + + static Future _open(EJDB2Builder b) async { + final hdl = + await _mc.invokeMethod('open', [null, b.path, b._asOpts()]).catchError(_errorHandler()); + return EJDB2._(hdl); + } + + /// Closes database instance. + Future close() { + if (_handle == null) { + throw StateError('Closed'); + } + return _mc.invokeMethod('close', [_handle]).catchError(_errorHandler()); + } + + /// Create instance of [query] specified for [collection]. + /// If [collection] is not specified a [query] spec must contain collection name, + /// eg: `@mycollection/[foo=bar]` + JQL createQuery(String query, [String collection]) => JQL._(this, collection, query); + + /// Create instance of [query]. + JQL operator [](String query) => createQuery(query); + + /// Save [json] document under specified [id] or create a new document + /// with new generated `id`. Returns future holding actual document `id`. + Future put(String collection, dynamic json, [int id]) => Future.sync(() => _mc + .invokeMethod('put', [ + _handle, + collection, + (json is! String) ? convert_lib.jsonEncode(json) : json as String, + id + ]) + .catchError(_errorHandler()) + .then((v) => v as int)); + + /// Apply rfc6902/rfc6901 JSON [patch] to the document identified by [id]. + Future patch(String collection, dynamic json, int id, [bool upsert = false]) => + Future.sync(() => _mc.invokeMethod('patch', [ + _handle, + collection, + (json is! String) ? convert_lib.jsonEncode(json) : json as String, + id, + upsert + ]).catchError(_errorHandler())); + + /// Apply JSON merge patch (rfc7396) to the document identified by `id` or + /// insert new document under specified `id`. + Future patchOrPut(String collection, dynamic json, int id) => + patch(collection, json, id, true); + + /// Get json body of document identified by [id] and stored in [collection]. + /// Throws [EJDB2Error] not found exception if document is not found. + Future get(String collection, int id) => _mc + .invokeMethod('get', [_handle, collection, id]) + .catchError(_errorHandler()) + .then((v) => JBDOC(id, v as String)); + + /// Get json body of document identified by [id] and stored in [collection]. + Future> getOptional(String collection, int id) { + return get(collection, id).then((doc) => Optional.of(doc)).catchError((err) { + if (err is EJDB2Error && err.isNotFound()) { + return Future.value(const Optional.absent()); + } + return Future.error(err); + }); + } + + /// Remove document identified by [id] from specified [collection]. + Future del(String collection, int id) => + _mc.invokeMethod('del', [_handle, collection, id]).catchError(_errorHandler()); + + /// Remove document identified by [id] from specified [collection]. + /// Doesn't raise error if document is not found. + Future delIgnoreNotFound(String collection, int id) => + del(collection, id).catchError((err) { + if (err is EJDB2Error && err.isNotFound()) { + return Future.value(); + } else { + return Future.error(err); + } + }); + + /// Get json body of database metadata. + Future info() => _mc + .invokeMethod('info', [_handle]) + .catchError(_errorHandler()) + .then((v) => convert_lib.jsonDecode(v as String)); + + /// Removes database [collection]. + Future removeCollection(String collection) => + _mc.invokeMethod('removeCollection', [_handle, collection]).catchError(_errorHandler()); + + /// Renames database collection. + Future renameCollection(String oldName, String newName) { + return _mc + .invokeMethod('renameCollection', [_handle, oldName, newName]).catchError(_errorHandler()); + } + + Future ensureStringIndex(String coll, String path, [bool unique]) => + _mc.invokeMethod('ensureStringIndex', [_handle, coll, path, unique ?? false]); + + Future removeStringIndex(String coll, String path, [bool unique]) => + _mc.invokeMethod('removeStringIndex', [_handle, coll, path, unique ?? false]); + + Future ensureIntIndex(String coll, String path, [bool unique]) => + _mc.invokeMethod('ensureIntIndex', [_handle, coll, path, unique ?? false]); + + Future removeIntIndex(String coll, String path, [bool unique]) => + _mc.invokeMethod('removeIntIndex', [_handle, coll, path, unique ?? false]); + + Future ensureFloatIndex(String coll, String path, [bool unique]) => + _mc.invokeMethod('ensureFloatIndex', [_handle, coll, path, unique ?? false]); + + Future removeFloatIndex(String coll, String path, [bool unique]) => + _mc.invokeMethod('removeFloatIndex', [_handle, coll, path, unique ?? false]); + + /// Creates an online database backup image and copies it into the specified [fileName]. + /// During online backup phase read/write database operations are allowed and not + /// blocked for significant amount of time. Returns future with backup + /// finish time as number of milliseconds since epoch. + Future onlineBackup(String fileName) => + _mc.invokeMethod('onlineBackup', [_handle, fileName]).then((v) => v as int); +} diff --git a/src/bindings/ejdb2_flutter/pubspec.yaml b/src/bindings/ejdb2_flutter/pubspec.yaml new file mode 100644 index 0000000..a0b6241 --- /dev/null +++ b/src/bindings/ejdb2_flutter/pubspec.yaml @@ -0,0 +1,30 @@ +name: ejdb2_flutter +description: Embeddable JSON Database engine EJDB http://ejdb.org Flutter binding. +version: 1.0.29+1 +homepage: https://ejdb.org + +environment: + sdk: '>=2.5.0 <3.0.0' + flutter: ">=1.10.0 <2.0.0" + +dependencies: + flutter: + sdk: flutter + json_at: ^1.0.4 + quiver: ^2.0.5 + pedantic: ^1.8.0+1 + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: + plugin: + platforms: + android: + package: com.softmotions.ejdb2 + pluginClass: Ejdb2FlutterPlugin + ios: + pluginClass: Ejdb2FlutterPlugin + + diff --git a/src/bindings/ejdb2_flutter/pubspec.yaml.in b/src/bindings/ejdb2_flutter/pubspec.yaml.in new file mode 100644 index 0000000..96b695f --- /dev/null +++ b/src/bindings/ejdb2_flutter/pubspec.yaml.in @@ -0,0 +1,30 @@ +name: ejdb2_flutter +description: Embeddable JSON Database engine EJDB http://ejdb.org Flutter binding. +version: @EJDB2_FLUTTER_VERSION@ +homepage: https://ejdb.org + +environment: + sdk: '>=2.5.0 <3.0.0' + flutter: ">=1.10.0 <2.0.0" + +dependencies: + flutter: + sdk: flutter + json_at: ^1.0.4 + quiver: ^2.0.5 + pedantic: ^1.8.0+1 + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: + plugin: + platforms: + android: + package: com.softmotions.ejdb2 + pluginClass: Ejdb2FlutterPlugin + ios: + pluginClass: Ejdb2FlutterPlugin + + diff --git a/src/bindings/ejdb2_flutter/version.txt b/src/bindings/ejdb2_flutter/version.txt new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/src/bindings/ejdb2_flutter/version.txt @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/src/bindings/ejdb2_jni/.editorconfig b/src/bindings/ejdb2_jni/.editorconfig new file mode 100644 index 0000000..8286f5f --- /dev/null +++ b/src/bindings/ejdb2_jni/.editorconfig @@ -0,0 +1,6 @@ +[*] +charset=utf-8 +end_of_line=lf +insert_final_newline=false +indent_style=space +indent_size=2 diff --git a/src/bindings/ejdb2_jni/.gitignore b/src/bindings/ejdb2_jni/.gitignore new file mode 100644 index 0000000..f0b9c6a --- /dev/null +++ b/src/bindings/ejdb2_jni/.gitignore @@ -0,0 +1,5 @@ + +### Idea All template +/.idea +*.iml +*.ipr \ No newline at end of file diff --git a/src/bindings/ejdb2_jni/.settings/org.eclipse.jdt.core.prefs b/src/bindings/ejdb2_jni/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..40df717 --- /dev/null +++ b/src/bindings/ejdb2_jni/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,10 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.processAnnotations=disabled +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=ignore \ No newline at end of file diff --git a/src/bindings/ejdb2_jni/CMakeLists.txt b/src/bindings/ejdb2_jni/CMakeLists.txt new file mode 100644 index 0000000..5531b08 --- /dev/null +++ b/src/bindings/ejdb2_jni/CMakeLists.txt @@ -0,0 +1,4 @@ +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/version.txt _VERSION) +set_property(GLOBAL PROPERTY EJDB2_JNI_VERSION_PROPERTY "${PROJECT_VERSION}.${_VERSION}") +set(EJDB2_JNI_VERSION "${PROJECT_VERSION}.${_VERSION}") +add_subdirectory(src) diff --git a/src/bindings/ejdb2_jni/README.md b/src/bindings/ejdb2_jni/README.md new file mode 100644 index 0000000..17bf5c4 --- /dev/null +++ b/src/bindings/ejdb2_jni/README.md @@ -0,0 +1,56 @@ +# EJDB2 Java JNI binding + +Embeddable JSON Database engine http://ejdb.org Java binding. + +See https://github.com/Softmotions/ejdb/blob/master/README.md + +For API usage examples take a look into [EJDB2Example.java](https://github.com/Softmotions/ejdb/blob/master/src/bindings/ejdb2_jni/example/src/main/java/EJDB2Example.java) and [TestEJDB2.java](https://github.com/Softmotions/ejdb/blob/master/src/bindings/ejdb2_jni/src/test/java/com/softmotions/ejdb2/TestEJDB2.java) classes. + +## Minimal example + +```java +public static void main(String[] args) { + try (EJDB2 db = new EJDB2Builder("example.db").truncate().open()) { + long id = db.put("parrots", "{\"name\":\"Bianca\", \"age\": 4}"); + System.out.println("Bianca record: " + id); + + id = db.put("parrots", "{\"name\":\"Darko\", \"age\": 8}"); + System.out.println("Darko record: " + id); + + db.createQuery("@parrots/[age > :age]").setLong("age", 3).execute((docId, doc) -> { + System.out.println(String.format("Found %d %s", docId, doc)); + return 1; + }); + } +} +``` + +## Supported platforms + +- Linux x64 +- MacOS + +## Install from Ubuntu PPA + +```sh +sudo add-apt-repository ppa:adamansky/ejdb2 +sudo apt-get update +sudo apt-get install ejdb2-java +``` + +Note: Yoy may need to specify `LD_LIBRARY_PATH` env for `java` in order to help JVM find where +the `libejdb2jni.so` library is located. For Linux systems `ejdb2-java` PPA debian package installs +shared library symlink to `/usr/java/packages/lib` folder listed as default library search +path for JVM so you can skip specifying `LD_LIBRARY_PATH` in that case. + +## How to build it manually + +```sh +git clone https://github.com/Softmotions/ejdb.git +cd ./ejdb +mkdir ./build && cd build +cmake .. -DBUILD_JNI_BINDING=ON -DCMAKE_BUILD_TYPE=Release +make +``` + +[Sample EJDB2 java project](./example) diff --git a/src/bindings/ejdb2_jni/example/README.md b/src/bindings/ejdb2_jni/example/README.md new file mode 100644 index 0000000..03bdb2e --- /dev/null +++ b/src/bindings/ejdb2_jni/example/README.md @@ -0,0 +1,55 @@ +## EJDB2 Java sample app + +First of all you need to get `libejdb2jni.so` native binding library for JVM. + +You can build it yourself + +```sh +git clone https://github.com/Softmotions/ejdb.git +cd ./ejdb +mkdir ./build && cd build +cmake .. -DBUILD_JNI_BINDING=ON -DCMAKE_BUILD_TYPE=Release +make +``` + +or install binary package from [our PPA repository](https://launchpad.net/~adamansky/+archive/ubuntu/ejdb2) + +```sh +sudo add-apt-repository ppa:adamansky/ejdb2 +sudo apt-get update +sudo apt-get install ejdb2-java +``` + +Then compile and run app (Linux x86-64) + +```sh +cd ejdb2_jni/example + +mvn clean compile +mvn exec:java -Dexec.cleanupDaemonThreads=false -Dexec.mainClass="EJDB2Example" +``` + +Note: Yoy may need to specify `LD_LIBRARY_PATH` env for `java` in order to help JVM find where +the `libejdb2jni.so` library is located. For Linux systems `ejdb2-java` PPA debian package installs +shared library symlink to `/usr/java/packages/lib` folder listed as default library search +path for JVM so you can skip specifying `LD_LIBRARY_PATH` in that case. + +### Maven project configuration + +```xml + + + repsy + EJDB2 Maven Repositoty on Repsy + https://repo.repsy.io/mvn/adamansky/softmotions + + + + + + softmotions + ejdb2 + 2.0.57.11 + + +``` \ No newline at end of file diff --git a/src/bindings/ejdb2_jni/example/pom.xml b/src/bindings/ejdb2_jni/example/pom.xml new file mode 100644 index 0000000..5b35d8d --- /dev/null +++ b/src/bindings/ejdb2_jni/example/pom.xml @@ -0,0 +1,48 @@ + + + + 4.0.0 + softmotions + ejdb2-example + 1.0-SNAPSHOT + jar + + + UTF-8 + 11 + 11 + + + + + repsy + EJDB2 Maven Repositoty on Repsy + https://repo.repsy.io/mvn/adamansky/softmotions + + + + + + softmotions + ejdb2 + 2.0.57.11 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.source.version} + ${java.target.version} + UTF-8 + + + + + \ No newline at end of file diff --git a/src/bindings/ejdb2_jni/example/src/main/java/EJDB2Example.java b/src/bindings/ejdb2_jni/example/src/main/java/EJDB2Example.java new file mode 100644 index 0000000..5cd8752 --- /dev/null +++ b/src/bindings/ejdb2_jni/example/src/main/java/EJDB2Example.java @@ -0,0 +1,25 @@ +import com.softmotions.ejdb2.EJDB2; +import com.softmotions.ejdb2.EJDB2Builder; + +/** + * Minimal EJDB2 Java example. + * + * @author Adamansky Anton (adamansky@softmotions.com) + */ +public class EJDB2Example { + + public static void main(String[] args) { + try (EJDB2 db = new EJDB2Builder("example.db").truncate().open()) { + long id = db.put("parrots", "{\"name\":\"Bianca\", \"age\": 4}"); + System.out.println("Bianca record: " + id); + + id = db.put("parrots", "{\"name\":\"Darko\", \"age\": 8}"); + System.out.println("Darko record: " + id); + + db.createQuery("@parrots/[age > :age]").setLong("age", 3).execute((doc) -> { + System.out.println(String.format("Found %s", doc)); + return 1; + }); + } + } +} diff --git a/src/bindings/ejdb2_jni/hints.txt b/src/bindings/ejdb2_jni/hints.txt new file mode 100644 index 0000000..0a75a5c --- /dev/null +++ b/src/bindings/ejdb2_jni/hints.txt @@ -0,0 +1,5 @@ +https://docs.oracle.com/en/java/javase/11/docs/specs/jni/ +(gdb) handle SIGSEGV nostop noprint pass + + + diff --git a/src/bindings/ejdb2_jni/pom.xml b/src/bindings/ejdb2_jni/pom.xml new file mode 100644 index 0000000..b7500d5 --- /dev/null +++ b/src/bindings/ejdb2_jni/pom.xml @@ -0,0 +1,32 @@ + + + + 4.0.0 + softmotions + ejdb2 + 1.0-SNAPSHOT + + UTF-8 + 11 + 11 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.source.version} + ${java.target.version} + UTF-8 + + + + + \ No newline at end of file diff --git a/src/bindings/ejdb2_jni/src/CMakeLists.txt b/src/bindings/ejdb2_jni/src/CMakeLists.txt new file mode 100644 index 0000000..528bfe5 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/CMakeLists.txt @@ -0,0 +1,185 @@ +include(FindJava) +include(UseJava) +include(FindJNI) + +if(NOT EJDB2_JNI_VERSION) + message(FATAL_ERROR "EJDB2_JNI_VERSION_PROPERTY Is not defined") +endif() + +set(SOURCES_JAR_FILE + ${CMAKE_CURRENT_BINARY_DIR}/ejdb2-${EJDB2_JNI_VERSION}-sources.jar) + +if(NOT JNI_FOUND) + if(ANDROID_PLATFORM) + list(FILTER JNI_INCLUDE_DIRS EXCLUDE REGEX NOTFOUND) + list(FILTER JNI_LIBRARIES EXCLUDE REGEX NOTFOUND) + list(LENGTH JNI_INCLUDE_DIRS _JNI_INCLUDE_DIRS_LEN) + if(_JNI_INCLUDE_DIRS_LEN LESS "1") + message(FATAL_ERROR "No JNI headers found ${JNI_INCLUDE_DIRS}") + endif() + else() + message(FATAL_ERROR "No JNI headers found ${JNI_INCLUDE_DIRS}") + endif() +endif() + +message("Java VERSION: ${Java_VERSION}") +message("Java EXECUTABLE: ${Java_JAVA_EXECUTABLE}") + +if(ANDROID_PLATFORM) + set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.8" "-target" "1.8") + + add_jar( + ejdb2jni_jar + SOURCES android/java/com/softmotions/ejdb2/EJDB2.java + android/java/com/softmotions/ejdb2/EJDB2Builder.java + android/java/com/softmotions/ejdb2/EJDB2Exception.java + android/java/com/softmotions/ejdb2/IWKVOptions.java + android/java/com/softmotions/ejdb2/JQL.java + android/java/com/softmotions/ejdb2/JQLCallback.java + VERSION ${EJDB2_JNI_VERSION} + OUTPUT_NAME + ejdb2 + GENERATE_NATIVE_HEADERS + ejdb2-jni-native-headers + DESTINATION + ${PROJECT_GENERATED_DIR}) + +else() + + if(${Java_VERSION} VERSION_LESS "11") + set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.8" "-target" "1.8") + else() + set(CMAKE_JAVA_COMPILE_FLAGS "-source" "11" "-target" "11") + endif() + + set(EJDB2_JAVA_SOURCES + com/softmotions/ejdb2/EJDB2.java + com/softmotions/ejdb2/EJDB2Builder.java + com/softmotions/ejdb2/EJDB2Document.java + com/softmotions/ejdb2/EJDB2DocumentCallback.java + com/softmotions/ejdb2/EJDB2Exception.java + com/softmotions/ejdb2/IWKVOptions.java + com/softmotions/ejdb2/JQL.java + com/softmotions/ejdb2/JQLCallback.java + com/softmotions/ejdb2/JSON.java + com/softmotions/ejdb2/JSONException.java) + + utils_list_prepend(EJDB2_JAVA_SOURCES_JAR "main/java/" ${EJDB2_JAVA_SOURCES}) + + add_jar( + ejdb2jni_jar + SOURCES ${EJDB2_JAVA_SOURCES_JAR} + VERSION ${EJDB2_JNI_VERSION} + OUTPUT_NAME ejdb2 GENERATE_NATIVE_HEADERS ejdb2-jni-native-headers + DESTINATION ${PROJECT_GENERATED_DIR}) + + add_custom_command( + TARGET ejdb2jni_jar + POST_BUILD + COMMAND jar -cf ${SOURCES_JAR_FILE} ${EJDB2_JAVA_SOURCES} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/main/java) + + get_target_property(DEPLOY_JAR_FILE ejdb2jni_jar JAR_FILE) + configure_file(maven-deploy.sh ${CMAKE_CURRENT_BINARY_DIR}/.. @ONLY) + +endif() + +install_jar(ejdb2jni_jar ${CMAKE_INSTALL_DATAROOTDIR}/java) + +include_directories(${JNI_INCLUDE_DIRS}) +add_library(ejdb2jni SHARED ejdb2jni.c) +add_dependencies(ejdb2jni ejdb2jni_jar) + +if(ANDROID) + set(JNI_LIBRARIES "-ldl -llog") +endif() + +if(${CMAKE_VERSION} VERSION_LESS "3.11") + create_javah( + TARGET ejdb2-jni-native-headers-old + CLASSES com.softmotions.ejdb2.EJDB2 com.softmotions.ejdb2.JQL + CLASSPATH ejdb2jni_jar + DEPENDS ejdb2jni_jar + OUTPUT_DIR ${PROJECT_GENERATED_DIR}) + add_dependencies(ejdb2jni ejdb2-jni-native-headers-old) + target_link_libraries(ejdb2jni PUBLIC ${JNI_LIBRARIES} ejdb2_s) +else() + target_link_libraries( + ejdb2jni + PUBLIC ${JNI_LIBRARIES} ejdb2_s + PRIVATE ejdb2-jni-native-headers) +endif() + +if(APPLE) + set_target_properties(ejdb2jni PROPERTIES SUFFIX ".jnilib") +endif() + +set_target_properties( + ejdb2jni + PROPERTIES VERSION ${EJDB2_JNI_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} + DEFINE_SYMBOL IW_API_EXPORTS) + +if(CMAKE_BUILD_TYPE STREQUAL "Release") + add_custom_command( + TARGET ejdb2jni + POST_BUILD + COMMAND strip -s $) +endif() + +install( + TARGETS ejdb2jni + FRAMEWORK DESTINATION ${FRAMEWORK_INSTALL_DIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) + +add_dependencies(ejdb2jni ejdb2jni_jar) + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + # Add symlink /usr/java/packages/lib/libejdb2jni.so -> installed + # libejdb2jni.so + set(_EJDB2JNI_PATH + ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/$ + ) + set(_EJDB2JNI_FILE + ${CMAKE_SHARED_LIBRARY_PREFIX}ejdb2jni${CMAKE_SHARED_LIBRARY_SUFFIX}) + + if(NOT POLICY CMP0087) + add_custom_command( + TARGET ejdb2jni + PRE_BUILD + COMMAND ln -sf ${_EJDB2JNI_PATH} ${CMAKE_BINARY_DIR}/${_EJDB2JNI_FILE}) + else() + install(CODE "execute_process( \ + COMMAND ln -fs ${_EJDB2JNI_PATH} ${_EJDB2JNI_FILE} \ + )") + endif() + + install(FILES ${CMAKE_BINARY_DIR}/${_EJDB2JNI_FILE} + DESTINATION /usr/java/packages/lib) +endif() + +# Test cases +if(BUILD_TESTS AND NOT ANDROID_PLATFORM) + add_jar( + ejdb2jni_test_jar + SOURCES test/java/com/softmotions/ejdb2/TestEJDB2.java + INCLUDE_JARS ejdb2jni_jar + OUTPUT_NAME ejdb2jni_tests) + + get_target_property(_EJDB2JNI_JAR ejdb2jni_jar JAR_FILE) + get_target_property(_EJDB2JNI_TEST_JAR ejdb2jni_test_jar JAR_FILE) + + message( + "Test: ${Java_JAVA_EXECUTABLE} -ea -Djava.library.path=. -cp ${_EJDB2JNI_JAR}:${_EJDB2JNI_TEST_JAR} com.softmotions.ejdb2.TestEJDB2" + ) + add_test( + NAME ejdb2jni + COMMAND + ${Java_JAVA_EXECUTABLE} -ea + # -verbose:jni + -Djava.library.path=. -cp ${_EJDB2JNI_JAR}:${_EJDB2JNI_TEST_JAR} + com.softmotions.ejdb2.TestEJDB2) + +endif() diff --git a/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/.gitkeep b/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/EJDB2.java b/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/EJDB2.java new file mode 100644 index 0000000..c8418ba --- /dev/null +++ b/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/EJDB2.java @@ -0,0 +1,407 @@ +package com.softmotions.ejdb2; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +/** + * EJDB2 JNI Wrapper. + *

+ * Database opened by {@link EJDB2Builder#open()} helper. + *

+ * Example: + * + *

+ * {@code
+ *  try {
+ *    EJDB2 db = new EJDB2Builder("my.db").open()
+ *    ...
+ *    db.close();
+ *  } catch (EJDB2Exception ex) {
+ *    ...
+ *  }
+ * }
+ * 
+ *

+ * In order to release memory resources and avoiding data lost every opened + * database instance should be closed with {@link EJDB2#close()}. + * + * @author Adamansky Anton (adamansky@softmotions.com) + */ +public final class EJDB2 { + + static { + System.loadLibrary("ejdb2jni"); + } + + @SuppressWarnings("unused") + private long _handle; + + private final EJDB2Builder options; + + /** + * Returns options used to build this database instance. + * + * @return + */ + public EJDB2Builder getOptions() { + return options; + } + + EJDB2(EJDB2Builder options) throws EJDB2Exception { + this.options = options; + _open(options); + } + + /** + * Closes database instance and releases all resources. + */ + public void close() { + _dispose(); + } + + /** + * Create a query instance. Query can be reused multiple times with various + * placeholder parameters. See JQL specification: + * https://github.com/Softmotions/ejdb/blob/master/README.md#jql + *

+ * Note: collection name must be encoded in query, eg: {@code @mycoll/[foo=bar]} + *

+ */ + public JQL createQuery(String query) throws EJDB2Exception { + return new JQL(this, query, null); + } + + /** + * Create a query instance. Query can be reused multiple times with various + * placeholder parameters. See JQL specification: + * https://github.com/Softmotions/ejdb/blob/master/README.md#jql + *

+ * If {@code collection} is not null it will be used for query. In this case + * s * collection name encoded in query will not be taken into account. + * + * @param collection Optional collection name + */ + public JQL createQuery(String query, String collection) throws EJDB2Exception { + return new JQL(this, query, collection); + } + + /** + * Persists {@code json} document into {@code collection}. + * + * @param collection Collection name + * @param json JSON document + * @return Generated identifier for document + * @throws EJDB2Exception + */ + public long put(String collection, String json) throws EJDB2Exception { + return _put(collection, json, 0); + } + + /** + * Persists {@code json} document under specified {@code id}. + *

+ * If {@code id} is zero a new document identifier will be generated. + * + * @param collection Collection name + * @param json JSON document + * @param id Document id. If zero a new identifier will be genareted + * @return Document identifier + * @throws EJDB2Exception + */ + public long put(String collection, String json, long id) throws EJDB2Exception { + return _put(collection, json, id); + } + + /** + * Removes a document identified by given {@code id} from collection + * {@code coll}. + *

+ * If document is not found {@link EJDB2Exception} will be thrown: + * + *

+   * {@code
+   *  code: 75001
+   *  message: Key not found. (IWKV_ERROR_NOTFOUND)
+   * }
+   * 
+ * + * @param collection Collection name + * @param id Document id + * @throws EJDB2Exception + */ + public void del(String collection, long id) throws EJDB2Exception { + _del(collection, id); + } + + public void renameCollection(String oldCollectionName, String newCollectionName) throws EJDB2Exception { + _rename_collection(oldCollectionName, newCollectionName); + } + + /** + * Apply rfc6902/rfc7386 JSON patch to the document identified by {@code id}. + * + * @param collection Collection name + * @param patch JSON patch + * @param id Document id + * @throws EJDB2Exception + */ + public void patch(String collection, String patch, long id) throws EJDB2Exception { + _patch(collection, patch, id, false); + } + + /** + * Apply JSON merge patch (rfc7396) to the document identified by `id` or + * insert new document under specified `id`. + */ + public void patchOrPut(String collection, String patch, long id) { + _patch(collection, patch, id, true); + } + + /** + * Write document identified by {@code id} into {@code out}. + *

+ * If document is not found {@link EJDB2Exception} will be thrown: + * + *

+   * {@code
+   *  code: 75001
+   *  message: Key not found. (IWKV_ERROR_NOTFOUND)
+   * }
+   * 
+ * + * @param collection + * @param id + * @param out + */ + public void get(String collection, long id, OutputStream out) { + _get(collection, id, out, false); + } + + /** + * Returns document identified by {@code id} as String. + *

+ * If document is not found {@link EJDB2Exception} will be thrown: + * + *

+   * {@code
+   *  code: 75001
+   *  message: Key not found. (IWKV_ERROR_NOTFOUND)
+   * }
+   * 
+ * + * @param collection + * @param id + */ + public String getAsString(String collection, long id) throws UnsupportedEncodingException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + get(collection, id, bos); + return bos.toString("UTF-8"); + } + + /** + * Same as {@link #get(String, long, OutputStream)} but returns prettified JSON + * document. + * + * @param collection Collection name + * @param id Document id + * @param out Output stream to write + */ + public void getPrettified(String collection, long id, OutputStream out) { + _get(collection, id, out, true); + } + + /** + * Returns JSON document describind database structure. + *

+ * Example database metadata: + * + *

+   * {@code
+   *   {
+   *    "version": "2.0.0", // EJDB engine version
+   *    "file": "db.jb",    // Path to storage file
+   *    "size": 16384,      // Storage file size in bytes
+   *    "collections": [    // List of collections
+   *     {
+   *      "name": "c1",     // Collection name
+   *      "dbid": 3,        // Collection database ID
+   *      "rnum": 2,        // Number of documents in collection
+   *      "indexes": [      // List of collections indexes
+   *       {
+   *        "ptr": "/n",    // rfc6901 JSON pointer to indexed field
+   *        "mode": 8,      // Index mode. Here is EJDB_IDX_I64
+   *        "idbf": 96,     // Index database flags. See iwdb_flags_t
+   *        "dbid": 4,      // Index database ID
+   *        "rnum": 2       // Number records stored in index database
+   *       }
+   *      ]
+   *     }
+   *    ]
+   *   }
+   * }
+   * 
+ * + * @param out Output stream to write + * @throws EJDB2Exception + */ + public void info(OutputStream out) throws EJDB2Exception { + _info(out); + } + + /** + * Returns JSON document describind database structure. Same as + * {@link #info(OutputStream)} but returns JSON data as string. + * + * @throws EJDB2Exception + */ + public String infoAsString() throws EJDB2Exception, UnsupportedEncodingException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + info(bos); + return bos.toString("UTF-8"); + } + + /** + * Removes {@code collection} from database and all its documents. + * + * @param collection Collection name + * @return Current database instance + * @throws EJDB2Exception + */ + public EJDB2 removeCollection(String collection) throws EJDB2Exception { + _remove_collection(collection); + return this; + } + + /** + * Create string index with specified parameters if it has not existed before. + *

+ * Where {@code path} must be fully specified as rfc6901 JSON pointer. + *

+ * Example document: + * + *

+   * {@code
+   *    "address" : {
+   *      "street": "High Street"
+   *    }
+   * }
+   * 
+ *

+ * Call {@code ensureStringIndex("mycoll", "/address/street", true)} in order to + * create unique index over all street names in nested address object. + * + * @param collection Collection name + * @param path JSON pointer path to indexed field + * @param unique If {@code true} an unique index will be created + * @return Current database instance + * @throws EJDB2Exception + */ + public EJDB2 ensureStringIndex(String collection, String path, boolean unique) throws EJDB2Exception { + _ensure_index(collection, path, 0x04 | (unique ? 0x01 : 0)); + return this; + } + + /** + * Removes collection index for JSON document field pointed by {@code path} + * + * @param collection Collection name + * @param path JSON pointer path to indexed field + * @param unique {@code true} for unique index + * @return + * @throws EJDB2Exception + */ + public EJDB2 removeStringIndex(String collection, String path, boolean unique) throws EJDB2Exception { + _remove_index(collection, path, 0x04 | (unique ? 0x01 : 0)); + return this; + } + + /** + * Create integer index with specified parameters if it has not existed before. + * + * @param collection Collection name + * @param path JSON pointer path to indexed field + * @param unique {@code true} for unique index + * @return + * @throws EJDB2Exception + */ + public EJDB2 ensureIntIndex(String collection, String path, boolean unique) throws EJDB2Exception { + _ensure_index(collection, path, 0x08 | (unique ? 0x01 : 0)); + return this; + } + + /** + * Removes collection index for JSON document field pointed by {@code path} + * + * @param collection Collection name + * @param path JSON pointer path to indexed field + * @param unique {@code true} for unique index + * @return + * @throws EJDB2Exception + */ + public EJDB2 removeIntIndex(String collection, String path, boolean unique) throws EJDB2Exception { + _remove_index(collection, path, 0x08 | (unique ? 0x01 : 0)); + return this; + } + + /** + * Create floating point number index with specified parameters if it has not + * existed before. + * + * @param collection Collection name + * @param path JSON pointer path to indexed field + * @param unique {@code true} for unique index + * @return + * @throws EJDB2Exception + */ + public EJDB2 ensureFloatIndex(String collection, String path, boolean unique) throws EJDB2Exception { + _ensure_index(collection, path, 0x10 | (unique ? 0x01 : 0)); + return this; + } + + public EJDB2 removeFloatIndex(String collection, String path, boolean unique) throws EJDB2Exception { + _remove_index(collection, path, 0x10 | (unique ? 0x01 : 0)); + return this; + } + + /** + * Creates an online database backup image and copies it into the specified `targetFile`. + * During online backup phase read/write database operations are not + * blocked for significant amount of time. Returns backup finish time as number of milliseconds since epoch. + * Online backup guaranties what all records before finish timestamp will + * be stored in backup image. Later, online backup image can be + * opened as ordinary database file. + * + * @param targetFile Backup file path + * @note In order to avoid deadlocks: close all opened database cursors + * before calling this method or do call in separate thread. + */ + public long onlineBackup(String targetFile) throws EJDB2Exception { + return _online_backup(targetFile); + } + + private native void _open(EJDB2Builder opts) throws EJDB2Exception; + + private native void _dispose() throws EJDB2Exception; + + private native long _put(String collection, String json, long id) throws EJDB2Exception; + + private native void _del(String collection, long id) throws EJDB2Exception; + + private native void _rename_collection(String oldCollectionName, String newCollectionName) throws EJDB2Exception; + + private native void _patch(String collection, String patch, long id, boolean upsert) throws EJDB2Exception; + + private native void _get(String collection, long id, OutputStream out, boolean pretty) throws EJDB2Exception; + + private native void _info(OutputStream out) throws EJDB2Exception; + + private native void _remove_collection(String collection) throws EJDB2Exception; + + private native void _ensure_index(String collection, String path, int mode) throws EJDB2Exception; + + private native void _remove_index(String collection, String path, int mode) throws EJDB2Exception; + + private native long _online_backup(String targetFile) throws EJDB2Exception; + +} diff --git a/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/EJDB2Builder.java b/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/EJDB2Builder.java new file mode 100644 index 0000000..b2d2b72 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/EJDB2Builder.java @@ -0,0 +1,204 @@ +package com.softmotions.ejdb2; + +import java.io.Serializable; + +/** + * @author Adamansky Anton (adamansky@softmotions.com) + */ +public class EJDB2Builder implements Serializable { + + private static final long serialVersionUID = 6475291112728462635L; + + private boolean no_wal; + + private int sort_buffer_sz; + + private int document_buffer_sz; + + private final IWKVOptions iwkv; + + private final EJDB2HttpOptions http; + + public IWKVOptions getIWKVOptions() { + return iwkv; + } + + public EJDB2HttpOptions getHttpOptions() { + return http; + } + + public boolean isNoWal() { + return no_wal; + } + + public int getSortBufferSize() { + return sort_buffer_sz; + } + + public int getDocumentBufferSize() { + return document_buffer_sz; + } + + public EJDB2Builder(String path) { + iwkv = new IWKVOptions(path); + http = new EJDB2HttpOptions(); + } + + public EJDB2Builder noWAL(boolean v) { + this.no_wal = v; + return this; + } + + public EJDB2Builder withWAL() { + this.no_wal = false; + return this; + } + + public EJDB2Builder sortBufferSize(int v) { + this.sort_buffer_sz = v; + return this; + } + + public EJDB2Builder documentBufferSize(int v) { + this.document_buffer_sz = v; + return this; + } + + public EJDB2Builder truncate() { + iwkv.truncate(); + return this; + } + + public EJDB2Builder readonly() { + iwkv.readonly(); + return this; + } + + EJDB2Builder fileLockFailFast(boolean v) { + iwkv.fileLockFailFast(v); + return this; + } + + EJDB2Builder randomSeed(long seed) { + iwkv.randomSeed(seed); + return this; + } + + EJDB2Builder walCRCOnCheckpoint(boolean v) { + iwkv.walCRCOnCheckpoint(v); + return this; + } + + EJDB2Builder walSavepointTimeoutSec(int v) { + iwkv.walSavepointTimeoutSec(v); + return this; + } + + EJDB2Builder walCheckpointTimeoutSec(int v) { + iwkv.walCheckpointTimeoutSec(v); + return this; + } + + EJDB2Builder walBufferSize(long v) { + iwkv.walBufferSize(v); + return this; + } + + EJDB2Builder walCheckpointBufferSize(long v) { + iwkv.walCheckpointBufferSize(v); + return this; + } + + public EJDB2Builder httpEnabled(boolean v) { + http.enabled = v; + return this; + } + + public EJDB2Builder httpPort(int v) { + http.port = v; + return this; + } + + public EJDB2Builder httpBind(String v) { + http.bind = v; + return this; + } + + public EJDB2Builder httpAccessToken(String v) { + http.access_token = v; + return this; + } + + public EJDB2Builder httpReadAnon(boolean v) { + http.read_anon = v; + return this; + } + + public EJDB2Builder httpMaxBodySize(int v) { + http.max_body_size = v; + return this; + } + + public EJDB2 open() { + return new EJDB2(this); + } + + @Override + public String toString() { + return new StringBuilder().append(EJDB2Builder.class.getSimpleName()).append("[") + .append("no_wal=").append(no_wal).append(", ") + .append("sort_buffer_sz=").append(sort_buffer_sz).append(", ") + .append("document_buffer_sz=").append(document_buffer_sz).append(", ") + .append("iwkv=").append(iwkv).append(", ") + .append("http=").append(http) + .append("]").toString(); + } + + public static class EJDB2HttpOptions implements Serializable { + + private static final long serialVersionUID = 5957416367411081107L; + + boolean enabled; + int port; + String bind; + String access_token; + boolean read_anon; + int max_body_size; + + public boolean isEnabled() { + return enabled; + } + + public int getPort() { + return port; + } + + public String getBind() { + return bind; + } + + public String getAccessToken() { + return access_token; + } + + boolean getReadAnon() { + return read_anon; + } + + int getMaxBodySize() { + return max_body_size; + } + + @Override + public String toString() { + return new StringBuilder().append(EJDB2HttpOptions.class.getSimpleName()).append("[") + .append("enabled=").append(enabled).append(", ") + .append("port=").append(port).append(", ") + .append("bind=").append(bind).append(", ") + .append("access_token=").append(access_token).append(", ") + .append("read_anon=").append(read_anon).append(", ") + .append("max_body_size=").append(max_body_size) + .append("]").toString(); + } + } +} diff --git a/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/EJDB2Exception.java b/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/EJDB2Exception.java new file mode 100644 index 0000000..8d2b6b9 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/EJDB2Exception.java @@ -0,0 +1,33 @@ +package com.softmotions.ejdb2; + +/** + * @author Adamansky Anton (adamansky@softmotions.com) + */ +public final class EJDB2Exception extends RuntimeException { + + private static final long serialVersionUID = 2380289380319769542L; + + private final long code; + + private final long errno; + + public long getCode() { + return code; + } + + public long getErrno() { + return errno; + } + + public EJDB2Exception(long code, long errno, String message, Throwable cause) { + super("@ejdb IWRC:" + code + " errno:" + errno + ' ' + message, cause); + this.code = code; + this.errno = errno; + } + + public EJDB2Exception(long code, long errno, String message) { + super("@ejdb IWRC:" + code + " errno:" + errno + ' ' + message); + this.code = code; + this.errno = errno; + } +} diff --git a/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/IWKVOptions.java b/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/IWKVOptions.java new file mode 100644 index 0000000..e4a4c49 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/IWKVOptions.java @@ -0,0 +1,149 @@ +package com.softmotions.ejdb2; + +import java.io.Serializable; + +/** + * @author Adamansky Anton (adamansky@softmotions.com) + */ +public final class IWKVOptions implements Serializable { + + private static final long serialVersionUID = -6138184402489045096L; + + private final String path; + + private long oflags; + + private long random_seed; + + private boolean file_lock_fail_fast = true; + + private final WALOptions wal = new WALOptions(); + + public String getPath() { + return path; + } + + public boolean isTruncate() { + return (oflags & 0x4) != 0; + } + + public boolean isReadOnly() { + return (oflags & 0x4) != 0; + } + + public long getRandomSeed() { + return random_seed; + } + + public boolean getFileLockFailFast() { + return file_lock_fail_fast; + } + + public WALOptions getWALOptions() { + return this.wal; + } + + public IWKVOptions(String path) { + this.path = path; + } + + IWKVOptions truncate() { + oflags |= 0x4; + return this; + } + + IWKVOptions readonly() { + oflags |= 0x2; + return this; + } + + IWKVOptions fileLockFailFast(boolean v) { + this.file_lock_fail_fast = v; + return this; + } + + IWKVOptions randomSeed(long seed) { + this.random_seed = seed; + return this; + } + + IWKVOptions walCRCOnCheckpoint(boolean v) { + wal.check_crc_on_checkpoint = v; + return this; + } + + IWKVOptions walSavepointTimeoutSec(int v) { + wal.savepoint_timeout_sec = v; + return this; + } + + IWKVOptions walCheckpointTimeoutSec(int v) { + wal.checkpoint_timeout_sec = v; + return this; + } + + IWKVOptions walBufferSize(long v) { + wal.buffer_sz = v; + return this; + } + + IWKVOptions walCheckpointBufferSize(long v) { + wal.checkpoint_buffer_sz = v; + return this; + } + + @Override + public String toString() { + return new StringBuilder() + .append(IWKVOptions.class.getSimpleName()).append("[") + .append("path=").append(path).append(", ") + .append("oflags=").append(oflags).append(", ") + .append("file_lock_fail_fast=").append(file_lock_fail_fast).append(", ") + .append("random_seed=").append(random_seed).append(", ") + .append("wal=").append(wal) + .append("]").toString(); + } + + public static final class WALOptions implements Serializable { + + private static final long serialVersionUID = 2406233154956721582L; + + private boolean check_crc_on_checkpoint; + private int savepoint_timeout_sec; + private int checkpoint_timeout_sec; + private long buffer_sz; + private long checkpoint_buffer_sz; + + public boolean getCheckCRCOnCheckpoint() { + return this.check_crc_on_checkpoint; + } + + public int getSavePointTimeoutSec() { + return this.savepoint_timeout_sec; + } + + public int getCheckPointTimeoutSec() { + return this.checkpoint_timeout_sec; + } + + public long getBufferSize() { + return this.buffer_sz; + } + + public long getCheckpointBufferSize() { + return this.checkpoint_buffer_sz; + } + + @Override + public String toString() { + return new StringBuilder() + .append(WALOptions.class.getSimpleName()).append("[") + .append("check_crc_on_checkpoint=").append(check_crc_on_checkpoint).append(", ") + .append("savepoint_timeout_sec=").append(savepoint_timeout_sec).append(", ") + .append("checkpoint_timeout_sec=").append(checkpoint_timeout_sec).append(", ") + .append("buffer_sz=").append(buffer_sz).append(", ") + .append("checkpoint_buffer_sz=").append(checkpoint_buffer_sz) + .append("]").toString(); + } + } +} diff --git a/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/JQL.java b/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/JQL.java new file mode 100644 index 0000000..0c7c290 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/JQL.java @@ -0,0 +1,453 @@ +package com.softmotions.ejdb2; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * EJDB2 Query specification. + *

+ * Query can be reused multiple times with various placeholder parameters. See + * JQL specification: + * https://github.com/Softmotions/ejdb/blob/master/README.md#jql + *

+ * Memory resources used by JQL instance can be released explicitly by + * {@link JQL#close()}. + *

+ * Note: If user did not close instance explicitly it will be + * freed anyway once jql object will be garbage collected. + *

+ * Typical usage: + * + *

+ * {@code
+ *    try {
+ *       q.execute((docId, doc) -> {
+ *         JQL q = db.createQuery("/[foo=:val]", "mycoll").setString("val", "bar")
+ *         System.out.println(String.format("Found %d %s", docId, doc));
+ *         return 1;
+ *       });
+ *    } catch (Exception ex) {
+ *      ...
+ *    }
+ * }
+ * 
+ */ +public final class JQL { + + private static final ReferenceQueue refQueue = new ReferenceQueue(); + + @SuppressWarnings("StaticCollection") + private static final Map refs = new ConcurrentHashMap(); + + private static final Thread cleanupThread = new Thread(new Runnable() { + @Override + public void run() { + while (true) { + try { + ((Reference) refQueue.remove()).cleanup(); + } catch (InterruptedException ignored) { + } + } + } + }); + + static { + cleanupThread.setDaemon(true); + cleanupThread.start(); + } + + private final EJDB2 db; + + private final String query; + + private String collection; + + private long skip; + + private long limit; + + private long _handle; + + private ByteArrayOutputStream explain; + + /** + * Owner database instance + */ + public EJDB2 getDb() { + return db; + } + + /** + * Query specification used to construct this query object. + */ + public String getQuery() { + return query; + } + + /** + * Collection name used for this query + */ + public String getCollection() { + return collection; + } + + public JQL setCollection(String collection) { + this.collection = collection; + return this; + } + + /** + * Turn on collecting of query execution log + * + * @see #getExplainLog() + */ + public JQL withExplain() { + explain = new ByteArrayOutputStream(); + return this; + } + + /** + * Turn off collecting of query execution log + * + * @see #getExplainLog() + */ + public JQL withNoExplain() { + explain = null; + return this; + } + + public String getExplainLog() throws UnsupportedEncodingException { + return explain != null ? explain.toString("UTF-8") : null; + } + + /** + * Number of records to skip. This parameter takes precedence over {@code skip} + * encoded in query spec. + * + * @return + */ + public JQL setSkip(long skip) { + this.skip = skip; + return this; + } + + public long getSkip() { + return skip > 0 ? skip : _get_skip(); + } + + /** + * Maximum number of records to retrive. This parameter takes precedence over + * {@code limit} encoded in query spec. + */ + public JQL setLimit(long limit) { + this.limit = limit; + return this; + } + + public long getLimit() { + return limit > 0 ? limit : _get_limit(); + } + + /** + * Set positional string parameter starting for {@code 0} index. + *

+ * Example: + * + *

+   * {@code
+   *  db.createQuery("/[foo=:?]", "mycoll").setString(0, "zaz")
+   * }
+   * 
+ * + * @param pos Zero based positional index + * @param val Value to set + * @return + * @throws EJDB2Exception + */ + public JQL setString(int pos, String val) throws EJDB2Exception { + _set_string(pos, null, val, 0); + return this; + } + + /** + * Set string parameter placeholder in query spec. + *

+ * Example: + * + *

+   * {@code
+   *  db.createQuery("/[foo=:val]", "mycoll").setString("val", "zaz");
+   * }
+   * 
+ * + * @param placeholder Placeholder name + * @param val Value to set + * @return + * @throws EJDB2Exception + */ + public JQL setString(String placeholder, String val) throws EJDB2Exception { + _set_string(0, placeholder, val, 0); + return this; + } + + public JQL setLong(int pos, long val) throws EJDB2Exception { + _set_long(pos, null, val); + return this; + } + + public JQL setLong(String placeholder, long val) throws EJDB2Exception { + _set_long(0, placeholder, val); + return this; + } + + public JQL setJSON(int pos, String json) throws EJDB2Exception { + _set_string(pos, null, json, 1); + return this; + } + + public JQL setJSON(String placeholder, String json) throws EJDB2Exception { + _set_string(0, placeholder, json, 1); + return this; + } + + public JQL setRegexp(int pos, String regexp) throws EJDB2Exception { + _set_string(pos, null, regexp, 2); + return this; + } + + public JQL setRegexp(String placeholder, String regexp) throws EJDB2Exception { + _set_string(0, placeholder, regexp, 2); + return this; + } + + public JQL setDouble(int pos, double val) throws EJDB2Exception { + _set_double(pos, null, val); + return this; + } + + public JQL setDouble(String placeholder, double val) throws EJDB2Exception { + _set_double(0, placeholder, val); + return this; + } + + public JQL setBoolean(int pos, boolean val) throws EJDB2Exception { + _set_boolean(pos, null, val); + return this; + } + + public JQL setBoolean(String placeholder, boolean val) throws EJDB2Exception { + _set_boolean(0, placeholder, val); + return this; + } + + public JQL setNull(int pos) throws EJDB2Exception { + _set_null(pos, null); + return this; + } + + public JQL setNull(String placeholder) throws EJDB2Exception { + _set_null(0, placeholder); + return this; + } + + /** + * Execute query without result set callback. + * + * @throws EJDB2Exception + */ + public void execute() throws EJDB2Exception { + if (explain != null) { + explain.reset(); + } + _execute(db, null, explain); + } + + /** + * Execute query and handle records by provided {@code cb} + * + * @param cb Optional callback SAM + * @throws EJDB2Exception + */ + public void execute(JQLCallback cb) throws EJDB2Exception { + if (explain != null) { + explain.reset(); + } + _execute(db, cb, explain); + } + + /** + * Get first record entry: {@code [documentId, json]} in results set. Entry will + * contain nulls if no records found. + */ + public Map.Entry first() { + final Long[] idh = {null}; + final String[] jsonh = {null}; + if (explain != null) { + explain.reset(); + } + _execute(db, new JQLCallback() { + @Override + public long onRecord(long id, String json) { + idh[0] = id; + jsonh[0] = json; + return 0; + } + }, explain); + return new Map.Entry() { + private Long _key = idh[0]; + private String _value = jsonh[0]; + + @Override + public Long getKey() { + return _key; + } + + @Override + public String getValue() { + return _value; + } + + @Override + public String setValue(String value) { + _value = value; + return _value; + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (o.getClass() != this.getClass()) { + return false; + } + Map.Entry entry = ((Map.Entry) o); + return entry.getKey().equals(this._key) && entry.getValue().equals(this._value); + } + + @Override + public int hashCode() { + return 0; + } + }; + } + + /** + * Get first document body as JSON string or null. + */ + public String firstJson() { + return first().getValue(); + } + + /** + * Get first document id ot null + */ + public Long firstId() { + return first().getKey(); + } + + /** + * Execute scalar query. + *

+ * Example: + * + *

+   * long count = db.createQuery("@mycoll/* | count").executeScalarInt();
+   * 
+ */ + public long executeScalarInt() { + if (explain != null) { + explain.reset(); + } + return _execute_scalar_long(db, explain); + } + + /** + * Reset data stored in positional placeholderss + */ + public void reset() { + if (explain != null) { + explain.reset(); + } + _reset(); + } + + /** + * Close query instance releasing memory resources + */ + public void close() throws Exception { + Reference ref = refs.get(_handle); + if (ref != null) { + ref.enqueue(); + } else { + long h = _handle; + if (h != 0) { + _destroy(h); + } + } + } + + JQL(EJDB2 db, String query, String collection) throws EJDB2Exception { + this.db = db; + this.query = query; + this.collection = collection; + _init(db, query, collection); + // noinspection InstanceVariableUsedBeforeInitialized + refs.put(_handle, new Reference(this, refQueue)); + } + + @Override + public String toString() { + return new StringBuilder().append(JQL.class.getSimpleName()).append("[") + .append("query=").append(query).append(", ") + .append("collection=").append(collection) + .append("]").toString(); + } + + private static class Reference extends WeakReference { + private long handle; + + Reference(JQL jql, ReferenceQueue rq) { + super(jql, rq); + handle = jql._handle; + } + + void cleanup() { + long h = handle; + handle = 0L; + if (h != 0) { + refs.remove(h); + _destroy(h); + } + } + } + + private static native void _destroy(long handle); + + private native void _init(EJDB2 db, String query, String collection); + + private native void _execute(EJDB2 db, JQLCallback cb, OutputStream explainLog); + + private native long _execute_scalar_long(EJDB2 db, OutputStream explainLog); + + private native void _reset(); + + private native long _get_limit(); + + private native long _get_skip(); + + private native void _set_string(int pos, String placeholder, String val, int type); + + private native void _set_long(int pos, String placeholder, long val); + + private native void _set_double(int pos, String placeholder, double val); + + private native void _set_boolean(int pos, String placeholder, boolean val); + + private native void _set_null(int pos, String placeholder); +} \ No newline at end of file diff --git a/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/JQLCallback.java b/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/JQLCallback.java new file mode 100644 index 0000000..28344f8 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/JQLCallback.java @@ -0,0 +1,27 @@ +package com.softmotions.ejdb2; + +/** + * SAM callback used iterate over query result set. + */ +public interface JQLCallback { + + /** + * Called on every JSON record in result set. + * + * Implementor can control iteration behavior by returning a step getting next + * record: + * + *
    + *
  • {@code 1} go to the next record
  • + *
  • {@code N} move forward by {@code N} records
  • + *
  • {@code -1} iterate current record again
  • + *
  • {@code -2} go to the previous record
  • + *
  • {@code 0} stop iteration
  • + *
+ * + * @param id Current document identifier + * @param json Document JSOn body as string + * @return Number of records to move + */ + long onRecord(long id, String json); +} \ No newline at end of file diff --git a/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/example/EJDB2Example.java b/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/example/EJDB2Example.java new file mode 100644 index 0000000..d6ce1d6 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/android/java/com/softmotions/ejdb2/example/EJDB2Example.java @@ -0,0 +1,36 @@ +package com.softmotions.ejdb2.example; + +import com.softmotions.ejdb2.EJDB2; +import com.softmotions.ejdb2.EJDB2Builder; +import com.softmotions.ejdb2.EJDB2Exception; +import com.softmotions.ejdb2.JQLCallback; + +/** + * Minimal EJDB2 Java example. + * + * @author Adamansky Anton (adamansky@softmotions.com) + */ +public class EJDB2Example { + + public static void main(String[] args) { + try { + EJDB2 db = new EJDB2Builder("example.db").truncate().open(); + long id = db.put("parrots", "{\"name\":\"Bianca\", \"age\": 4}"); + System.out.println("Bianca record: " + id); + + id = db.put("parrots", "{\"name\":\"Darko\", \"age\": 8}"); + System.out.println("Darko record: " + id); + + db.createQuery("@parrots/[age > :age]").setLong("age", 3).execute(new JQLCallback() { + @Override + public long onRecord(long docId, String doc) { + System.out.println(String.format("Found %d %s", docId, doc)); + return 1; + } + }); + db.close(); + } catch (EJDB2Exception ex) { + System.out.println(String.format("Error %s", ex.getMessage())); + } + } +} \ No newline at end of file diff --git a/src/bindings/ejdb2_jni/src/ejdb2jni.c b/src/bindings/ejdb2_jni/src/ejdb2jni.c new file mode 100644 index 0000000..e3183e0 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/ejdb2jni.c @@ -0,0 +1,1228 @@ +#include +#include +#include +#include +#include + +#include "com_softmotions_ejdb2_EJDB2.h" +#include "com_softmotions_ejdb2_JQL.h" + + +#define JBN_JSON_FLUSH_BUFFER_SZ 4096 + +typedef struct JBN_STR { + const char *utf; + jstring str; +} JBN_STR; + +typedef enum { + _JBN_ERROR_START = (IW_ERROR_START + 15000UL + 5000), + JBN_ERROR_INVALID_FIELD, /**< Failed to get class field (JBN_ERROR_INVALID_FIELD) */ + JBN_ERROR_INVALID_METHOD, /**< Failed to get class method (JBN_ERROR_INVALID_METHOD) */ + JBN_ERROR_INVALID_OPTIONS, + /**< Invalid com.softmotions.ejdb2.EJDB2Builder configuration provided + (JBN_ERROR_INVALID_OPTIONS) */ + JBN_ERROR_INVALID_STATE, /**< Invalid com.softmotions.ejdb2.EJDB2 JNI state (JBN_ERROR_INVALID_STATE) */ + JBN_ERROR_CREATION_OBJ, /**< Failed to create/allocate JNI object (JBN_ERROR_CREATION_OBJ) */ + _JBN_ERROR_END, +} jbn_ecode_t; + +static jclass k_EJDB2Exception_clazz; +static jmethodID k_EJDB2Exception_constructor; // EJDB2Exception(int code, String message) + +static jclass k_EJDB2_clazz; +static jfieldID k_EJDB2_handle_fid; + +static jclass k_JQL_clazz; +static jfieldID k_JQL_handle_fid; +static jfieldID k_JQL_db_fid; +static jfieldID k_JQL_query_fid; +static jfieldID k_JQL_collection_fid; +static jfieldID k_JQL_skip_fid; +static jfieldID k_JQL_limit_fid; + +#define JBNFIELD(fid_, env_, clazz_, name_, type_) \ + fid_ = (*(env_))->GetFieldID(env_, clazz_, name_, type_); + +#define JBNFIELD2(fid_, env_, clazz_, name_, type_, label_) \ + fid_ = (*(env_))->GetFieldID(env_, clazz_, name_, type_); \ + if (!fid_) goto label_; + +typedef struct JBN_JSPRINT_CTX { + int flush_buffer_sz; + IWXSTR *xstr; + iwrc (*flushFn)(struct JBN_JSPRINT_CTX *pctx); + JNIEnv *env; + jclass osClazz; + jobject osObj; + jmethodID write_mid; +} JBN_JSPRINT_CTX; + +static iwrc jbn_json_printer(const char *data, int size, char ch, int count, void *op) { + JBN_JSPRINT_CTX *pctx = op; + IWXSTR *xstr = pctx->xstr; + if (!data) { + if (count) { + for (int i = 0; i < count; ++i) { + iwrc rc = iwxstr_cat(xstr, &ch, 1); + RCRET(rc); + } + } + } else { + if (size < 0) { + size = (int) strlen(data); + } + if (!count) { + count = 1; + } + for (int i = 0; i < count; ++i) { + iwrc rc = iwxstr_cat(xstr, data, size); + RCRET(rc); + } + } + if (iwxstr_size(xstr) >= pctx->flush_buffer_sz) { + iwrc rc = pctx->flushFn(pctx); + RCRET(rc); + } + return 0; +} + +IW_INLINE iwrc jbn_db(JNIEnv *env, jobject thisObj, EJDB *db) { + *db = 0; + jlong ptr = (*env)->GetLongField(env, thisObj, k_EJDB2_handle_fid); + if (!ptr) { + return JBN_ERROR_INVALID_STATE; + } + *db = (void*) ptr; + return 0; +} + +IW_INLINE iwrc jbn_jql_q(JNIEnv *env, jobject thisObj, JQL *q) { + *q = 0; + jlong ptr = (*env)->GetLongField(env, thisObj, k_JQL_handle_fid); + if (!ptr) { + return JBN_ERROR_INVALID_STATE; + } + *q = (void*) ptr; + return 0; +} + +static iwrc jbn_flush_to_stream(JBN_JSPRINT_CTX *pctx) { + JNIEnv *env = pctx->env; + IWXSTR *xstr = pctx->xstr; + size_t xsz = iwxstr_size(xstr); + if (xsz == 0) { + return 0; + } + jbyteArray arr = (*env)->NewByteArray(env, xsz); + if (!arr) { + return JBN_ERROR_CREATION_OBJ; + } + (*env)->SetByteArrayRegion(env, arr, 0, xsz, (void*) iwxstr_ptr(xstr)); + iwxstr_clear(xstr); + (*env)->CallVoidMethod(env, pctx->osObj, pctx->write_mid, arr); + return 0; +} + +static iwrc jbn_init_pctx(JNIEnv *env, JBN_JSPRINT_CTX *pctx, jobject thisObj, jobject osObj) { + memset(pctx, 0, sizeof(*pctx)); + iwrc rc = 0; + jclass osClazz = (*env)->GetObjectClass(env, osObj); + jmethodID writeMid = (*env)->GetMethodID(env, osClazz, "write", "([B)V"); + IWXSTR *xstr = iwxstr_new(); + if (!xstr) { + return iwrc_set_errno(rc, IW_ERROR_ALLOC); + } + pctx->xstr = xstr; + pctx->flush_buffer_sz = JBN_JSON_FLUSH_BUFFER_SZ; + pctx->env = env; + pctx->osClazz = osClazz; + pctx->osObj = osObj; + pctx->write_mid = writeMid; + pctx->flushFn = jbn_flush_to_stream; + return rc; +} + +static void jbn_destroy_pctx(JBN_JSPRINT_CTX *pctx) { + if (pctx->xstr) { + iwxstr_destroy(pctx->xstr); + pctx->xstr = 0; + } +} + +static void jbn_throw_rc_exception(JNIEnv *env, iwrc rc, const char *msg_) { + const char *msg; + if (msg_) { + msg = msg_; + } else { + msg = iwlog_ecode_explained(rc); + if (!msg) { + msg = "Unknown iwrc error"; + } + } + uint32_t eno = iwrc_strip_errno(&rc); + jstring msgStr = (*env)->NewStringUTF(env, msg); + jobject exObj = (*env)->NewObject(env, k_EJDB2Exception_clazz, + k_EJDB2Exception_constructor, (jlong) rc, (jlong) eno, msgStr); + if ((*env)->Throw(env, exObj) < 0) { + iwlog_error("Failed to throw exception for EJDB2Exception: %s", msg); + } +} + +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_EJDB2__1open( + JNIEnv *env, + jobject thisObj, + jobject optsObj) { + iwrc rc = 0; + EJDB_OPTS opts = { 0 }; + JNIEnv e = *env; + jfieldID fid; + jobject iwkv, http, wal; + jclass iwkvClazz, httpClazz, walClazz; + jclass optsClazz = e->GetObjectClass(env, optsObj); + + int sc = 0; + EJDB db = 0; + JBN_STR strings[3] = { 0 }; + + // opts + JBNFIELD(fid, env, optsClazz, "no_wal", "Z"); + opts.no_wal = e->GetBooleanField(env, optsObj, fid); + + JBNFIELD(fid, env, optsClazz, "sort_buffer_sz", "I"); + opts.sort_buffer_sz = (uint32_t) e->GetIntField(env, optsObj, fid); + + JBNFIELD(fid, env, optsClazz, "document_buffer_sz", "I"); + opts.document_buffer_sz = (uint32_t) e->GetIntField(env, optsObj, fid); + + // iwkv + JBNFIELD(fid, env, optsClazz, "iwkv", "Lcom/softmotions/ejdb2/IWKVOptions;"); + iwkv = e->GetObjectField(env, optsObj, fid); + if (!iwkv) { + jbn_throw_rc_exception(env, JBN_ERROR_INVALID_OPTIONS, 0); + return; + } + iwkvClazz = e->GetObjectClass(env, iwkv); + + JBNFIELD(fid, env, iwkvClazz, "random_seed", "J"); + opts.kv.random_seed = (uint32_t) e->GetLongField(env, iwkv, fid); + + JBNFIELD(fid, env, iwkvClazz, "oflags", "J"); + opts.kv.oflags = (iwkv_openflags) e->GetLongField(env, iwkv, fid); + + JBNFIELD(fid, env, iwkvClazz, "file_lock_fail_fast", "Z"); + opts.kv.file_lock_fail_fast = e->GetBooleanField(env, iwkv, fid); + + JBNFIELD(fid, env, iwkvClazz, "path", "Ljava/lang/String;"); + strings[sc].str = e->GetObjectField(env, iwkv, fid); + strings[sc].utf = strings[sc].str ? e->GetStringUTFChars(env, strings[sc].str, 0) : 0; + opts.kv.path = strings[sc++].utf; + if (!opts.kv.path) { + rc = JBN_ERROR_INVALID_OPTIONS; + goto finish; + } + + // wal + JBNFIELD2(fid, env, iwkvClazz, "wal", "Lcom/softmotions/ejdb2/IWKVOptions$WALOptions;", finish); + wal = e->GetObjectField(env, iwkv, fid); + if (!wal) { + jbn_throw_rc_exception(env, JBN_ERROR_INVALID_OPTIONS, 0); + goto finish; + } + walClazz = e->GetObjectClass(env, wal); + + JBNFIELD2(fid, env, walClazz, "check_crc_on_checkpoint", "Z", finish); + opts.kv.wal.check_crc_on_checkpoint = e->GetBooleanField(env, wal, fid); + + JBNFIELD2(fid, env, walClazz, "savepoint_timeout_sec", "I", finish); + opts.kv.wal.savepoint_timeout_sec = (uint32_t) e->GetIntField(env, wal, fid); + + JBNFIELD2(fid, env, walClazz, "checkpoint_timeout_sec", "I", finish); + opts.kv.wal.checkpoint_timeout_sec = (uint32_t) e->GetIntField(env, wal, fid); + + JBNFIELD2(fid, env, walClazz, "buffer_sz", "J", finish); + opts.kv.wal.wal_buffer_sz = (uint64_t) e->GetLongField(env, wal, fid); + + JBNFIELD2(fid, env, walClazz, "checkpoint_buffer_sz", "J", finish); + opts.kv.wal.checkpoint_buffer_sz = (uint64_t) e->GetLongField(env, wal, fid); + + + // http + JBNFIELD2(fid, env, optsClazz, "http", "Lcom/softmotions/ejdb2/EJDB2Builder$EJDB2HttpOptions;", finish); + http = e->GetObjectField(env, optsObj, fid); + httpClazz = e->GetObjectClass(env, http); + + JBNFIELD2(fid, env, httpClazz, "enabled", "Z", finish); + opts.http.enabled = e->GetBooleanField(env, http, fid); + + JBNFIELD2(fid, env, httpClazz, "port", "I", finish); + opts.http.port = e->GetIntField(env, http, fid); + + JBNFIELD2(fid, env, httpClazz, "bind", "Ljava/lang/String;", finish); + strings[sc].str = e->GetObjectField(env, http, fid); + strings[sc].utf = strings[sc].str ? e->GetStringUTFChars(env, strings[sc].str, 0) : 0; + opts.http.bind = strings[sc++].utf; + + JBNFIELD2(fid, env, httpClazz, "access_token", "Ljava/lang/String;", finish); + strings[sc].str = e->GetObjectField(env, http, fid); + strings[sc].utf = strings[sc].str ? e->GetStringUTFChars(env, strings[sc].str, 0) : 0; + opts.http.access_token = strings[sc++].utf; + opts.http.access_token_len = opts.http.access_token ? strlen(opts.http.access_token) : 0; + + JBNFIELD2(fid, env, httpClazz, "read_anon", "Z", finish); + opts.http.read_anon = e->GetBooleanField(env, http, fid); + + JBNFIELD2(fid, env, httpClazz, "max_body_size", "I", finish); + opts.http.max_body_size = (size_t) e->GetIntField(env, http, fid); + + rc = ejdb_open(&opts, &db); + RCGO(rc, finish); + + e->SetLongField(env, thisObj, k_EJDB2_handle_fid, (jlong) db); + +finish: + for (int i = 0; i < (sizeof(strings) / sizeof(strings[0])); ++i) { + if (strings[i].str) { + e->ReleaseStringUTFChars(env, strings[i].str, strings[i].utf); + } + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } +} + +// DISPOSE +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_EJDB2__1dispose(JNIEnv *env, jobject thisObj) { + jlong ptr = (*env)->GetLongField(env, thisObj, k_EJDB2_handle_fid); + if (ptr) { + (*env)->SetLongField(env, thisObj, k_EJDB2_handle_fid, 0); + EJDB db = (void*) ptr; + iwrc rc = ejdb_close(&db); + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } + } +} + +// PUT +JNIEXPORT jlong JNICALL Java_com_softmotions_ejdb2_EJDB2__1put( + JNIEnv *env, + jobject thisObj, + jstring coll_, + jstring json_, + jlong id) { + iwrc rc; + EJDB db; + JBL jbl = 0; + jlong ret = id; + + const char *coll = (*env)->GetStringUTFChars(env, coll_, 0); + const char *json = (*env)->GetStringUTFChars(env, json_, 0); + if (!coll || !json) { + rc = IW_ERROR_INVALID_ARGS; + goto finish; + } + + rc = jbn_db(env, thisObj, &db); + RCGO(rc, finish); + + rc = jbl_from_json(&jbl, json); + RCGO(rc, finish); + + if (id > 0) { + rc = ejdb_put(db, coll, jbl, id); + } else { + rc = ejdb_put_new(db, coll, jbl, &ret); + } + +finish: + if (jbl) { + jbl_destroy(&jbl); + } + if (coll) { + (*env)->ReleaseStringUTFChars(env, coll_, coll); + } + if (json) { + (*env)->ReleaseStringUTFChars(env, json_, json); + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } + return ret; +} + +JNIEXPORT jlong JNICALL Java_com_softmotions_ejdb2_EJDB2__1online_1backup( + JNIEnv *env, jobject thisObj, + jstring target_) { + EJDB db; + uint64_t ts = 0; + const char *target = (*env)->GetStringUTFChars(env, target_, 0); + + iwrc rc = jbn_db(env, thisObj, &db); + RCGO(rc, finish); + + rc = ejdb_online_backup(db, &ts, target); + +finish: + if (target) { + (*env)->ReleaseStringUTFChars(env, target_, target); + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } + return ts; +} + +// GET +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_EJDB2__1get( + JNIEnv *env, + jobject thisObj, + jstring coll_, + jlong id, + jobject osObj, + jboolean pretty) { + iwrc rc; + EJDB db; + JBL jbl = 0; + JBN_JSPRINT_CTX pctx; + + const char *coll = (*env)->GetStringUTFChars(env, coll_, 0); + if (!coll) { + rc = IW_ERROR_INVALID_ARGS; + goto finish; + } + + rc = jbn_db(env, thisObj, &db); + RCGO(rc, finish); + + rc = jbn_init_pctx(env, &pctx, thisObj, osObj); + RCGO(rc, finish); + + rc = ejdb_get(db, coll, (int64_t) id, &jbl); + RCGO(rc, finish); + + rc = jbl_as_json(jbl, jbn_json_printer, &pctx, 0); + RCGO(rc, finish); + + rc = pctx.flushFn(&pctx); + +finish: + if (coll) { + (*env)->ReleaseStringUTFChars(env, coll_, coll); + } + if (jbl) { + jbl_destroy(&jbl); + } + jbn_destroy_pctx(&pctx); + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } +} + +// INFO +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_EJDB2__1info( + JNIEnv *env, + jobject thisObj, + jobject osObj) { + iwrc rc; + EJDB db; + JBL jbl = 0; + JBN_JSPRINT_CTX pctx; + + rc = jbn_db(env, thisObj, &db); + RCGO(rc, finish); + + rc = jbn_init_pctx(env, &pctx, thisObj, osObj); + RCGO(rc, finish); + + rc = ejdb_get_meta(db, &jbl); + RCGO(rc, finish); + + rc = jbl_as_json(jbl, jbn_json_printer, &pctx, 0); + RCGO(rc, finish); + + rc = pctx.flushFn(&pctx); + +finish: + if (jbl) { + jbl_destroy(&jbl); + } + jbn_destroy_pctx(&pctx); + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } +} + +// DEL +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_EJDB2__1del( + JNIEnv *env, + jobject thisObj, + jstring coll_, + jlong id) { + iwrc rc; + EJDB db; + const char *coll = (*env)->GetStringUTFChars(env, coll_, 0); + if (!coll) { + rc = IW_ERROR_INVALID_ARGS; + goto finish; + } + rc = jbn_db(env, thisObj, &db); + RCGO(rc, finish); + + rc = ejdb_del(db, coll, (int64_t) id); + +finish: + if (coll) { + (*env)->ReleaseStringUTFChars(env, coll_, coll); + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } +} + +// RENAME COLLECTION +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_EJDB2__1rename_1collection( + JNIEnv *env, jobject thisObj, + jstring oldColl_, jstring newColl_) { + iwrc rc; + EJDB db; + const char *newColl = 0; + const char *oldColl = (*env)->GetStringUTFChars(env, oldColl_, 0); + if (!oldColl) { + rc = IW_ERROR_INVALID_ARGS; + goto finish; + } + newColl = (*env)->GetStringUTFChars(env, newColl_, 0); + if (!newColl) { + rc = IW_ERROR_INVALID_ARGS; + goto finish; + } + rc = jbn_db(env, thisObj, &db); + RCGO(rc, finish); + + rc = ejdb_rename_collection(db, oldColl, newColl); + +finish: + if (oldColl) { + (*env)->ReleaseStringUTFChars(env, oldColl_, oldColl); + } + if (newColl) { + (*env)->ReleaseStringUTFChars(env, newColl_, newColl); + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } +} + +// PATCH +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_EJDB2__1patch( + JNIEnv *env, + jobject thisObj, + jstring coll_, + jstring patch_, + jlong id, + jboolean upsert) { + iwrc rc; + EJDB db; + const char *coll = (*env)->GetStringUTFChars(env, coll_, 0); + const char *patch = (*env)->GetStringUTFChars(env, patch_, 0); + if (!coll || !patch) { + rc = IW_ERROR_INVALID_ARGS; + goto finish; + } + rc = jbn_db(env, thisObj, &db); + RCGO(rc, finish); + + if (upsert) { + rc = ejdb_merge_or_put(db, coll, patch, (int64_t) id); + } else { + rc = ejdb_patch(db, coll, patch, (int64_t) id); + } + +finish: + if (coll) { + (*env)->ReleaseStringUTFChars(env, coll_, coll); + } + if (patch_) { + (*env)->ReleaseStringUTFChars(env, patch_, patch); + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } +} + +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_EJDB2__1remove_1collection( + JNIEnv *env, + jobject thisObj, + jstring coll_) { + iwrc rc; + EJDB db; + const char *coll = (*env)->GetStringUTFChars(env, coll_, 0); + if (!coll) { + rc = IW_ERROR_INVALID_ARGS; + goto finish; + } + rc = jbn_db(env, thisObj, &db); + RCGO(rc, finish); + + rc = ejdb_remove_collection(db, coll); + +finish: + if (coll) { + (*env)->ReleaseStringUTFChars(env, coll_, coll); + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } +} + +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_EJDB2__1ensure_1index( + JNIEnv *env, + jobject thisObj, + jstring coll_, + jstring path_, + jint mode) { + + iwrc rc; + EJDB db; + const char *coll = (*env)->GetStringUTFChars(env, coll_, 0); + const char *path = (*env)->GetStringUTFChars(env, path_, 0); + if (!coll || !path) { + rc = IW_ERROR_INVALID_ARGS; + goto finish; + } + rc = jbn_db(env, thisObj, &db); + RCGO(rc, finish); + + rc = ejdb_ensure_index(db, coll, path, (ejdb_idx_mode_t) mode); + +finish: + if (coll) { + (*env)->ReleaseStringUTFChars(env, coll_, coll); + } + if (path) { + (*env)->ReleaseStringUTFChars(env, path_, path); + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } +} + +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_EJDB2__1remove_1index( + JNIEnv *env, + jobject thisObj, + jstring coll_, + jstring path_, + jint mode) { + iwrc rc; + EJDB db; + const char *coll = (*env)->GetStringUTFChars(env, coll_, 0); + const char *path = (*env)->GetStringUTFChars(env, path_, 0); + if (!coll || !path) { + rc = IW_ERROR_INVALID_ARGS; + goto finish; + } + rc = jbn_db(env, thisObj, &db); + RCGO(rc, finish); + + rc = ejdb_remove_index(db, coll, path, (ejdb_idx_mode_t) mode); + +finish: + if (coll) { + (*env)->ReleaseStringUTFChars(env, coll_, coll); + } + if (path) { + (*env)->ReleaseStringUTFChars(env, path_, path); + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } +} + +// JQL INIT +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_JQL__1init( + JNIEnv *env, + jobject thisObj, + jobject dbObj, + jstring queryStr, + jstring collStr) { + EJDB db; + iwrc rc; + JQL q = 0; + const char *query = 0, *coll = 0; + + if (!dbObj || !queryStr) { + rc = IW_ERROR_INVALID_ARGS; + goto finish; + } + + rc = jbn_db(env, dbObj, &db); + RCGO(rc, finish); + + query = (*env)->GetStringUTFChars(env, queryStr, 0); + if (!query) { + rc = IW_ERROR_INVALID_ARGS; + goto finish; + } + + if (collStr) { + coll = (*env)->GetStringUTFChars(env, collStr, 0); + } + rc = jql_create2(&q, coll, query, JQL_KEEP_QUERY_ON_PARSE_ERROR | JQL_SILENT_ON_PARSE_ERROR); + RCGO(rc, finish); + + (*env)->SetLongField(env, thisObj, k_JQL_handle_fid, (jlong) q); + if (!coll) { + collStr = (*env)->NewStringUTF(env, jql_collection(q)); + (*env)->SetObjectField(env, thisObj, k_JQL_collection_fid, collStr); + } + +finish: + if (query) { + (*env)->ReleaseStringUTFChars(env, queryStr, query); + } + if (coll) { + (*env)->ReleaseStringUTFChars(env, collStr, coll); + } + if (rc) { + if (q && (rc == JQL_ERROR_QUERY_PARSE)) { + jbn_throw_rc_exception(env, rc, jql_error(q)); + } else { + jbn_throw_rc_exception(env, rc, 0); + } + if (q) { + jql_destroy(&q); + } + } +} + +// JQL RESET +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_JQL__1reset(JNIEnv *env, jobject thisObj) { + jlong ptr = (*env)->GetLongField(env, thisObj, k_JQL_handle_fid); + if (ptr) { + JQL q = (void*) ptr; + jql_reset(q, true, true); + } +} + +// JQL DESTROY +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_JQL__1destroy(JNIEnv *env, jclass clazz, jlong handle) { + if (handle) { + JQL q = (void*) handle; + jql_destroy(&q); + } +} + +typedef struct JBN_EXEC_CTX { + JNIEnv *env; + jobject cbObj; + jclass cbClazz; + jmethodID cbMid; +} JBN_EXEC_CTX; + +static iwrc jbn_exec_visitor(struct _EJDB_EXEC *ux, EJDB_DOC doc, int64_t *step) { + iwrc rc = 0; + jstring json = 0; + JBN_EXEC_CTX *ectx = ux->opaque; + JNIEnv *env = ectx->env; + IWXSTR *xstr = iwxstr_new2(jbl_size(doc->raw) * 2); + if (!xstr) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + if (doc->node) { + rc = jbn_as_json(doc->node, jbl_xstr_json_printer, xstr, 0); + } else { + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + } + RCGO(rc, finish); + + json = (*env)->NewStringUTF(env, iwxstr_ptr(xstr)); + if (!json) { + if (!(*env)->ExceptionOccurred(env)) { + rc = JBN_ERROR_CREATION_OBJ; + } + goto finish; + } + int64_t llv = (*env)->CallLongMethod(env, ectx->cbObj, ectx->cbMid, (jlong) doc->id, json); + if (llv < -2) { + *step = 0; + } else { + *step = llv; + } + +finish: + if (json) { + (*env)->DeleteLocalRef(env, json); + } + iwxstr_destroy(xstr); + return rc; +} + +// JQL EXECUTE +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_JQL__1execute( + JNIEnv *env, + jobject thisObj, + jobject dbObj, + jobject cbObj, + jobject logStreamObj) { + iwrc rc; + EJDB db; + JQL q; + IWXSTR *log = 0; + + if (!dbObj) { + jbn_throw_rc_exception(env, IW_ERROR_INVALID_ARGS, 0); + return; + } + + rc = jbn_jql_q(env, thisObj, &q); + RCGO(rc, finish); + + rc = jbn_db(env, dbObj, &db); + RCGO(rc, finish); + + JBN_EXEC_CTX ectx = { + .env = env, + .cbObj = cbObj + }; + + if (cbObj) { + ectx.cbClazz = (*env)->GetObjectClass(env, cbObj); + ectx.cbMid = (*env)->GetMethodID(env, ectx.cbClazz, "onRecord", "(JLjava/lang/String;)J"); + if (!ectx.cbMid) { + goto finish; + } + } + + jlong skip = (*env)->GetLongField(env, thisObj, k_JQL_skip_fid); + jlong limit = (*env)->GetLongField(env, thisObj, k_JQL_limit_fid); + if (logStreamObj) { + log = iwxstr_new(); + if (!log) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + } + + EJDB_EXEC ux = { + .db = db, + .q = q, + .skip = skip > 0 ? skip : 0, + .limit = limit > 0 ? limit : 0, + .opaque = &ectx, + .visitor = cbObj ? jbn_exec_visitor : 0, + .log = log + }; + + rc = ejdb_exec(&ux); + RCGO(rc, finish); + + if (log) { // Send query execution log + size_t xsz = iwxstr_size(log); + jclass logStreamClazz = (*env)->GetObjectClass(env, logStreamObj); + jmethodID writeMid = (*env)->GetMethodID(env, logStreamClazz, "write", "([B)V"); + if (!writeMid) { + goto finish; + } + jbyteArray arr = (*env)->NewByteArray(env, xsz); + if (!arr) { + goto finish; + } + (*env)->SetByteArrayRegion(env, arr, 0, xsz, (void*) iwxstr_ptr(log)); + (*env)->CallVoidMethod(env, logStreamObj, writeMid, arr); + } + +finish: + if (log) { + iwxstr_destroy(log); + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } +} + +// JQL EXECUTE SCALAR LONG +JNIEXPORT jlong JNICALL Java_com_softmotions_ejdb2_JQL__1execute_1scalar_1long( + JNIEnv *env, + jobject thisObj, + jobject dbObj, + jobject logStreamObj) { + iwrc rc; + EJDB db; + JQL q; + IWXSTR *log = 0; + jlong ret = 0; + + if (!dbObj) { + jbn_throw_rc_exception(env, IW_ERROR_INVALID_ARGS, 0); + return 0; + } + + rc = jbn_jql_q(env, thisObj, &q); + RCGO(rc, finish); + + rc = jbn_db(env, dbObj, &db); + RCGO(rc, finish); + + jlong skip = (*env)->GetLongField(env, thisObj, k_JQL_skip_fid); + jlong limit = (*env)->GetLongField(env, thisObj, k_JQL_limit_fid); + if (logStreamObj) { + log = iwxstr_new(); + if (!log) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + } + + EJDB_EXEC ux = { + .db = db, + .q = q, + .skip = skip > 0 ? skip : 0, + .limit = limit > 0 ? limit : 0, + .log = log + }; + + rc = ejdb_exec(&ux); + RCGO(rc, finish); + + if (log) { // Send query execution log + size_t xsz = iwxstr_size(log); + jclass logStreamClazz = (*env)->GetObjectClass(env, logStreamObj); + jmethodID writeMid = (*env)->GetMethodID(env, logStreamClazz, "write", "([B)V"); + if (!writeMid) { + goto finish; + } + jbyteArray arr = (*env)->NewByteArray(env, xsz); + if (!arr) { + goto finish; + } + (*env)->SetByteArrayRegion(env, arr, 0, xsz, (void*) iwxstr_ptr(log)); + (*env)->CallVoidMethod(env, logStreamObj, writeMid, arr); + } + + ret = ux.cnt; + +finish: + if (log) { + iwxstr_destroy(log); + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } + return ret; +} + +static void jbn_free_json_node(void *ptr, void *op) { + IWPOOL *pool = op; + if (pool) { + iwpool_destroy(pool); + } +} + +static void jbn_free_str(void *ptr, void *op) { + free(ptr); +} + +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_JQL__1set_1string( + JNIEnv *env, + jobject thisObj, + jint pos, + jstring placeholder_, + jstring val_, + jint type) { + JQL q; + iwrc rc; + const char *placeholder = 0, *val; + + if (!val_) { + jbn_throw_rc_exception(env, IW_ERROR_INVALID_ARGS, 0); + return; + } + val = (*env)->GetStringUTFChars(env, val_, 0); + + rc = jbn_jql_q(env, thisObj, &q); + RCGO(rc, finish); + + if (placeholder_) { + placeholder = (*env)->GetStringUTFChars(env, placeholder_, 0); + } + if (type == 1) { // JSON + JBL_NODE node; + IWPOOL *pool = iwpool_create(1024); + if (!pool) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + rc = jbn_from_json(val, &node, pool); + if (rc) { + iwpool_destroy(pool); + goto finish; + } + rc = jql_set_json2(q, placeholder, pos, node, jbn_free_json_node, pool); + if (rc) { + iwpool_destroy(pool); + goto finish; + } + } else if (type == 2) { // Regexp + char *str = strdup(val); + if (!str) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + rc = jql_set_regexp2(q, placeholder, pos, str, jbn_free_str, 0); + if (rc) { + free(str); + goto finish; + } + } else { // All other cases + char *str = strdup(val); + if (!str) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + rc = jql_set_str2(q, placeholder, pos, str, jbn_free_str, 0); + if (rc) { + free(str); + goto finish; + } + } + +finish: + if (val) { + (*env)->ReleaseStringUTFChars(env, val_, val); + } + if (placeholder) { + (*env)->ReleaseStringUTFChars(env, placeholder_, placeholder); + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } +} + +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_JQL__1set_1long( + JNIEnv *env, + jobject thisObj, + jint pos, + jstring placeholder_, + jlong val) { + + JQL q; + iwrc rc; + const char *placeholder = 0; + + rc = jbn_jql_q(env, thisObj, &q); + RCGO(rc, finish); + + if (placeholder_) { + placeholder = (*env)->GetStringUTFChars(env, placeholder_, 0); + } + + rc = jql_set_i64(q, placeholder, pos, val); + +finish: + if (placeholder) { + (*env)->ReleaseStringUTFChars(env, placeholder_, placeholder); + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } +} + +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_JQL__1set_1double( + JNIEnv *env, + jobject thisObj, + jint pos, + jstring placeholder_, + jdouble val) { + JQL q; + iwrc rc; + const char *placeholder = 0; + + rc = jbn_jql_q(env, thisObj, &q); + RCGO(rc, finish); + + if (placeholder_) { + placeholder = (*env)->GetStringUTFChars(env, placeholder_, 0); + } + + rc = jql_set_f64(q, placeholder, pos, val); + +finish: + if (placeholder) { + (*env)->ReleaseStringUTFChars(env, placeholder_, placeholder); + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } +} + +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_JQL__1set_1boolean( + JNIEnv *env, + jobject thisObj, + jint pos, + jstring placeholder_, + jboolean val) { + JQL q; + iwrc rc; + const char *placeholder = 0; + + rc = jbn_jql_q(env, thisObj, &q); + RCGO(rc, finish); + + if (placeholder_) { + placeholder = (*env)->GetStringUTFChars(env, placeholder_, 0); + } + + rc = jql_set_bool(q, placeholder, pos, val); + +finish: + if (placeholder) { + (*env)->ReleaseStringUTFChars(env, placeholder_, placeholder); + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } +} + +JNIEXPORT void JNICALL Java_com_softmotions_ejdb2_JQL__1set_1null( + JNIEnv *env, + jobject thisObj, + jint pos, + jstring placeholder_) { + JQL q; + iwrc rc; + const char *placeholder = 0; + + rc = jbn_jql_q(env, thisObj, &q); + RCGO(rc, finish); + + if (placeholder_) { + placeholder = (*env)->GetStringUTFChars(env, placeholder_, 0); + } + + rc = jql_set_null(q, placeholder, pos); + +finish: + if (placeholder) { + (*env)->ReleaseStringUTFChars(env, placeholder_, placeholder); + } + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } +} + +JNIEXPORT jlong JNICALL Java_com_softmotions_ejdb2_JQL__1get_1limit(JNIEnv *env, jobject thisObj) { + JQL q; + int64_t limit = 0; + iwrc rc = jbn_jql_q(env, thisObj, &q); + RCGO(rc, finish); + rc = jql_get_limit(q, &limit); + +finish: + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } + return (jlong) limit; +} + +JNIEXPORT jlong JNICALL Java_com_softmotions_ejdb2_JQL__1get_1skip(JNIEnv *env, jobject thisObj) { + JQL q; + int64_t skip = 0; + iwrc rc = jbn_jql_q(env, thisObj, &q); + RCGO(rc, finish); + rc = jql_get_skip(q, &skip); + +finish: + if (rc) { + jbn_throw_rc_exception(env, rc, 0); + } + return (jlong) skip; +} + +static const char *jbn_ecodefn(locale_t locale, uint32_t ecode) { + if (!((ecode > _JBN_ERROR_START) && (ecode < _JBN_ERROR_END))) { + return 0; + } + switch (ecode) { + case JBN_ERROR_INVALID_FIELD: + return "Failed to get class field (JBN_ERROR_INVALID_FIELD)"; + case JBN_ERROR_INVALID_METHOD: + return "Failed to get class method (JBN_ERROR_INVALID_METHOD)"; + case JBN_ERROR_INVALID_OPTIONS: + return "Invalid com.softmotions.ejdb2.EJDB2Builder configuration provided (JBN_ERROR_INVALID_OPTIONS)"; + case JBN_ERROR_INVALID_STATE: + return "Invalid com.softmotions.ejdb2.EJDB2 JNI state. Database closed? (JBN_ERROR_INVALID_STATE)"; + case JBN_ERROR_CREATION_OBJ: + return "Failed to create/allocate JNI object (JBN_ERROR_CREATION_OBJ)"; + } + return 0; +} + +JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv *env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK) { + return -1; + } + static volatile int jbn_ecodefn_initialized = 0; + if (__sync_bool_compare_and_swap(&jbn_ecodefn_initialized, 0, 1)) { + iwrc rc = ejdb_init(); + if (rc) { + iwlog_ecode_error3(rc); + return JNI_ERR; + } + iwlog_register_ecodefn(jbn_ecodefn); + } + + jclass clazz = (*env)->FindClass(env, "com/softmotions/ejdb2/EJDB2"); + if (!clazz) { + iwlog_error2("Cannot find com.softmotions.ejdb2.EJDB2 class"); + return -1; + } + k_EJDB2_clazz = (*env)->NewGlobalRef(env, clazz); + k_EJDB2_handle_fid = (*env)->GetFieldID(env, k_EJDB2_clazz, "_handle", "J"); + + + clazz = (*env)->FindClass(env, "com/softmotions/ejdb2/EJDB2Exception"); + if (!clazz) { + iwlog_error2("Cannot find com.softmotions.ejdb2.EJDB2Exception class"); + return -1; + } + k_EJDB2Exception_clazz = (*env)->NewGlobalRef(env, clazz); + k_EJDB2Exception_constructor = (*env)->GetMethodID(env, k_EJDB2Exception_clazz, + "", "(JJLjava/lang/String;)V"); + if (!k_EJDB2Exception_constructor) { + iwlog_error2("Cannot find com.softmotions.ejdb2.EJDB2Exception#(long,String)"); + return -1; + } + + clazz = (*env)->FindClass(env, "com/softmotions/ejdb2/JQL"); + if (!clazz) { + iwlog_error2("Cannot find com.softmotions.ejdb2.JQL class"); + return -1; + } + k_JQL_clazz = (*env)->NewGlobalRef(env, clazz); + k_JQL_handle_fid = (*env)->GetFieldID(env, k_JQL_clazz, "_handle", "J"); + k_JQL_db_fid = (*env)->GetFieldID(env, k_JQL_clazz, "db", "Lcom/softmotions/ejdb2/EJDB2;"); + k_JQL_query_fid = (*env)->GetFieldID(env, k_JQL_clazz, "query", "Ljava/lang/String;"); + k_JQL_collection_fid = (*env)->GetFieldID(env, k_JQL_clazz, "collection", "Ljava/lang/String;"); + k_JQL_skip_fid = (*env)->GetFieldID(env, k_JQL_clazz, "skip", "J"); + k_JQL_limit_fid = (*env)->GetFieldID(env, k_JQL_clazz, "limit", "J"); + + return JNI_VERSION_1_6; +} + +JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) { // Not really useless + JNIEnv *env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK) { + return; + } + if (k_EJDB2_clazz) { + (*env)->DeleteGlobalRef(env, k_EJDB2_clazz); + } + if (k_JQL_clazz) { + (*env)->DeleteGlobalRef(env, k_JQL_clazz); + } + if (k_EJDB2Exception_clazz) { + (*env)->DeleteGlobalRef(env, k_EJDB2Exception_clazz); + } +} diff --git a/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/EJDB2.java b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/EJDB2.java new file mode 100644 index 0000000..e7e4724 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/EJDB2.java @@ -0,0 +1,517 @@ +package com.softmotions.ejdb2; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +/** + * EJDB2 JNI Wrapper. + *

+ * Database opened by {@link EJDB2Builder#open()} helper. + *

+ * Example: + * + *

+ * {@code
+ *  try (EJDB2 db = new EJDB2Builder("my.db").open() {
+ *    ...
+ *  }
+ * }
+ * 
+ *

+ * In order to release memory resources and avoiding data lost every opened + * database instance should be closed with {@link EJDB2#close()}. + * + * @author Adamansky Anton (adamansky@softmotions.com) + */ +public final class EJDB2 implements AutoCloseable { + + static { + System.loadLibrary("ejdb2jni"); + } + + @SuppressWarnings("unused") + private long _handle; + + private final EJDB2Builder options; + + /** + * Returns options used to build this database instance. + * + * @return + */ + public EJDB2Builder getOptions() { + return options; + } + + EJDB2(EJDB2Builder options) throws EJDB2Exception { + this.options = options; + _open(options); + } + + /** + * Closes database instance and releases all resources. + */ + @Override + public void close() { + _dispose(); + } + + /** + * Create a query instance. Query can be reused multiple times with various + * placeholder parameters. See JQL specification: + * https://github.com/Softmotions/ejdb/blob/master/README.md#jql + *

+ * Note: collection name must be encoded in query, eg: {@code @mycoll/[foo=bar]} + *

+ */ + public JQL createQuery(String query) throws EJDB2Exception { + return new JQL(this, query, null); + } + + /** + * Create a query instance. Query can be reused multiple times with various + * placeholder parameters. See JQL specification: + * https://github.com/Softmotions/ejdb/blob/master/README.md#jql + *

+ * If {@code collection} is not null it will be used for query. In this case s * + * collection name encoded in query will not be taken into account. + * + * @param collection Optional collection name + */ + public JQL createQuery(String query, String collection) throws EJDB2Exception { + return new JQL(this, query, collection); + } + + /** + * Persists {@code json} document into {@code collection}. + * + * @param collection Collection name + * @param json JSON document + * @return Generated identifier for document + * @throws EJDB2Exception + */ + public long put(String collection, String json) throws EJDB2Exception { + return _put(collection, json, 0); + } + + /** + * Persists {@code json} document into {@code collection}. + * + * @param collection Collection name + * @param json JSON document + * @return Generated identifier for document + * @throws EJDB2Exception + */ + public long put(String collection, JSON json) throws EJDB2Exception { + return _put(collection, json.toString(), 0); + } + + /** + * Persists {@code json} document under specified {@code id}. + *

+ * If {@code id} is zero a new document identifier will be generated. + * + * @param collection Collection name + * @param json JSON document + * @param id Document id. If zero a new identifier will be + * genareted + * @return Document identifier + * @throws EJDB2Exception + */ + public long put(String collection, String json, long id) throws EJDB2Exception { + return _put(collection, json, id); + } + + /** + * Persists {@code json} document under specified {@code id}. + *

+ * If {@code id} is zero a new document identifier will be generated. + * + * @param collection Collection name + * @param json JSON document + * @param id Document id. If zero a new identifier will be + * genareted + * @return Document identifier + * @throws EJDB2Exception + */ + public long put(String collection, JSON json, long id) throws EJDB2Exception { + return _put(collection, json.toString(), id); + } + + /** + * Removes a document identified by given {@code id} from collection + * {@code coll}. + *

+ * If document is not found {@link EJDB2Exception} will be thrown: + * + *

+   * {@code
+   *  code: 75001
+   *  message: Key not found. (IWKV_ERROR_NOTFOUND)
+   * }
+   * 
+ * + * @param collection Collection name + * @param id Document id + * @throws EJDB2Exception + */ + public void del(String collection, long id) throws EJDB2Exception { + _del(collection, id); + } + + public void renameCollection(String oldCollectionName, String newCollectionName) throws EJDB2Exception { + _rename_collection(oldCollectionName, newCollectionName); + } + + /** + * Apply rfc6902/rfc7386 JSON patch to the document identified by {@code id}. + * + * @param collection Collection name + * @param patch JSON patch + * @param id Document id + * @throws EJDB2Exception + */ + public void patch(String collection, String patch, long id) throws EJDB2Exception { + _patch(collection, patch, id, false); + } + + /** + * Apply rfc6902/rfc7386 JSON patch to the document identified by {@code id}. + * + * @param collection Collection name + * @param patch JSON patch + * @param id Document id + * @throws EJDB2Exception + */ + public void patch(String collection, JSON patch, long id) throws EJDB2Exception { + _patch(collection, patch.toString(), id, false); + } + + /** + * Apply JSON merge patch (rfc7396) to the document identified by `id` or insert + * new document under specified `id`. + */ + public void patchOrPut(String collection, String patch, long id) { + _patch(collection, patch, id, true); + } + + /** + * Apply JSON merge patch (rfc7396) to the document identified by `id` or insert + * new document under specified `id`. + */ + public void patchOrPut(String collection, JSON patch, long id) { + _patch(collection, patch.toString(), id, true); + } + + /** + * Write document identified by {@code id} into {@code out}. + *

+ * If document is not found {@link EJDB2Exception} will be thrown: + * + *

+   * {@code
+   *  code: 75001
+   *  message: Key not found. (IWKV_ERROR_NOTFOUND)
+   * }
+   * 
+ * + * @param collection + * @param id + * @param out + */ + public void get(String collection, long id, OutputStream out) { + _get(collection, id, out, false); + } + + /** + * Returns document identified by {@code id} as String. + *

+ * If document is not found {@link EJDB2Exception} will be thrown: + * + *

+   * {@code
+   *  code: 75001
+   *  message: Key not found. (IWKV_ERROR_NOTFOUND)
+   * }
+   * 
+ * + * @param collection + * @param id + */ + public String getAsString(String collection, long id) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + get(collection, id, bos); + try { + return bos.toString("UTF-8"); + } catch (UnsupportedEncodingException ignored) { + return null; + } + } + + /** + * Returns document identified by {@code id} as {@link EJDB2Document} object. + *

+ * If document is not found {@link EJDB2Exception} will be thrown: + * + *

+   * {@code
+   *  code: 75001
+   *  message: Key not found. (IWKV_ERROR_NOTFOUND)
+   * }
+   * 
+ * + * @param collection + * @param id + */ + public EJDB2Document getAsDocument(String collection, long id) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + get(collection, id, bos); + return new EJDB2Document(id, bos.toByteArray()); + } + + /** + * Returns document identified by {@code id} as {@link JSON} object. + *

+ * If document is not found {@link EJDB2Exception} will be thrown: + * + *

+   * {@code
+   *  code: 75001
+   *  message: Key not found. (IWKV_ERROR_NOTFOUND)
+   * }
+   * 
+ * + * @param collection + * @param id + */ + public JSON getAsJSON(String collection, long id) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + get(collection, id, bos); + return JSON.fromBytes(bos.toByteArray()); + } + + /** + * Same as {@link #get(String, long, OutputStream)} but returns prettified JSON + * document. + * + * @param collection Collection name + * @param id Document id + * @param out Output stream to write + */ + public void getPrettified(String collection, long id, OutputStream out) { + _get(collection, id, out, true); + } + + /** + * Returns JSON document describind database structure. + *

+ * Example database metadata: + * + *

+   * {@code
+   *   {
+   *    "version": "2.0.0", // EJDB engine version
+   *    "file": "db.jb",    // Path to storage file
+   *    "size": 16384,      // Storage file size in bytes
+   *    "collections": [    // List of collections
+   *     {
+   *      "name": "c1",     // Collection name
+   *      "dbid": 3,        // Collection database ID
+   *      "rnum": 2,        // Number of documents in collection
+   *      "indexes": [      // List of collections indexes
+   *       {
+   *        "ptr": "/n",    // rfc6901 JSON pointer to indexed field
+   *        "mode": 8,      // Index mode. Here is EJDB_IDX_I64
+   *        "idbf": 96,     // Index database flags. See iwdb_flags_t
+   *        "dbid": 4,      // Index database ID
+   *        "rnum": 2       // Number records stored in index database
+   *       }
+   *      ]
+   *     }
+   *    ]
+   *   }
+   * }
+   * 
+ * + * @param out Output stream to write + * @throws EJDB2Exception + */ + public void info(OutputStream out) throws EJDB2Exception { + _info(out); + } + + /** + * Returns JSON document describind database structure. Same as + * {@link #info(OutputStream)} but returns JSON data as string. + * + * @throws EJDB2Exception + */ + public String infoAsString() throws EJDB2Exception { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + info(bos); + try { + return bos.toString("UTF-8"); + } catch (UnsupportedEncodingException ignored) { + return null; + } + } + + /** + * Returns {@link JSON} document describind database structure. Same as + * {@link #info(OutputStream)} but returns JSON data as {@link JSON}. + * + * @throws EJDB2Exception + */ + public JSON infoAsJSON() throws EJDB2Exception { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + info(bos); + return JSON.fromBytes(bos.toByteArray()); + } + + /** + * Removes {@code collection} from database and all its documents. + * + * @param collection Collection name + * @return Current database instance + * @throws EJDB2Exception + */ + public EJDB2 removeCollection(String collection) throws EJDB2Exception { + _remove_collection(collection); + return this; + } + + /** + * Create string index with specified parameters if it has not existed before. + *

+ * Where {@code path} must be fully specified as rfc6901 JSON pointer. + *

+ * Example document: + * + *

+   * {@code
+   *    "address" : {
+   *      "street": "High Street"
+   *    }
+   * }
+   * 
+ *

+ * Call {@code ensureStringIndex("mycoll", "/address/street", true)} in order to + * create unique index over all street names in nested address object. + * + * @param collection Collection name + * @param path JSON pointer path to indexed field + * @param unique If {@code true} an unique index will be created + * @return Current database instance + * @throws EJDB2Exception + */ + public EJDB2 ensureStringIndex(String collection, String path, boolean unique) throws EJDB2Exception { + _ensure_index(collection, path, 0x04 | (unique ? 0x01 : 0)); + return this; + } + + /** + * Removes collection index for JSON document field pointed by {@code path} + * + * @param collection Collection name + * @param path JSON pointer path to indexed field + * @param unique {@code true} for unique index + * @return + * @throws EJDB2Exception + */ + public EJDB2 removeStringIndex(String collection, String path, boolean unique) throws EJDB2Exception { + _remove_index(collection, path, 0x04 | (unique ? 0x01 : 0)); + return this; + } + + /** + * Create integer index with specified parameters if it has not existed before. + * + * @param collection Collection name + * @param path JSON pointer path to indexed field + * @param unique {@code true} for unique index + * @return + * @throws EJDB2Exception + */ + public EJDB2 ensureIntIndex(String collection, String path, boolean unique) throws EJDB2Exception { + _ensure_index(collection, path, 0x08 | (unique ? 0x01 : 0)); + return this; + } + + /** + * Removes collection index for JSON document field pointed by {@code path} + * + * @param collection Collection name + * @param path JSON pointer path to indexed field + * @param unique {@code true} for unique index + * @return + * @throws EJDB2Exception + */ + public EJDB2 removeIntIndex(String collection, String path, boolean unique) throws EJDB2Exception { + _remove_index(collection, path, 0x08 | (unique ? 0x01 : 0)); + return this; + } + + /** + * Create floating point number index with specified parameters if it has not + * existed before. + * + * @param collection Collection name + * @param path JSON pointer path to indexed field + * @param unique {@code true} for unique index + * @return + * @throws EJDB2Exception + */ + public EJDB2 ensureFloatIndex(String collection, String path, boolean unique) throws EJDB2Exception { + _ensure_index(collection, path, 0x10 | (unique ? 0x01 : 0)); + return this; + } + + public EJDB2 removeFloatIndex(String collection, String path, boolean unique) throws EJDB2Exception { + _remove_index(collection, path, 0x10 | (unique ? 0x01 : 0)); + return this; + } + + /** + * Creates an online database backup image and copies it into the specified + * `targetFile`. During online backup phase read/write database operations are + * allowed and not blocked for significant amount of time. Returns backup finish + * time as number of milliseconds since epoch. + * + * Online backup guaranties what all records before finish timestamp will be + * stored in backup image. Later, online backup image can be opened as ordinary + * database file. + * + * @param targetFile Backup file path + * @note In order to avoid deadlocks: close all opened database + * cursors before calling this method or do call in separate + * thread. + */ + public long onlineBackup(String targetFile) throws EJDB2Exception { + return _online_backup(targetFile); + } + + private native void _open(EJDB2Builder opts) throws EJDB2Exception; + + private native void _dispose() throws EJDB2Exception; + + private native long _put(String collection, String json, long id) throws EJDB2Exception; + + private native void _del(String collection, long id) throws EJDB2Exception; + + private native void _rename_collection(String oldCollectionName, String newCollectionName) throws EJDB2Exception; + + private native void _patch(String collection, String patch, long id, boolean upsert) throws EJDB2Exception; + + private native void _get(String collection, long id, OutputStream out, boolean pretty) throws EJDB2Exception; + + private native void _info(OutputStream out) throws EJDB2Exception; + + private native void _remove_collection(String collection) throws EJDB2Exception; + + private native void _ensure_index(String collection, String path, int mode) throws EJDB2Exception; + + private native void _remove_index(String collection, String path, int mode) throws EJDB2Exception; + + private native long _online_backup(String targetFile) throws EJDB2Exception; +} diff --git a/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/EJDB2Builder.java b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/EJDB2Builder.java new file mode 100644 index 0000000..a2be715 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/EJDB2Builder.java @@ -0,0 +1,196 @@ +package com.softmotions.ejdb2; + +import java.io.Serializable; +import java.util.StringJoiner; + +/** + * @author Adamansky Anton (adamansky@softmotions.com) + */ +public class EJDB2Builder implements Serializable { + + private static final long serialVersionUID = 6475291112728462635L; + + private boolean no_wal; + + private int sort_buffer_sz; + + private int document_buffer_sz; + + private final IWKVOptions iwkv; + + private final EJDB2HttpOptions http; + + public IWKVOptions getIWKVOptions() { + return iwkv; + } + + public EJDB2HttpOptions getHttpOptions() { + return http; + } + + public boolean isNoWal() { + return no_wal; + } + + public int getSortBufferSize() { + return sort_buffer_sz; + } + + public int getDocumentBufferSize() { + return document_buffer_sz; + } + + public EJDB2Builder(String path) { + iwkv = new IWKVOptions(path); + http = new EJDB2HttpOptions(); + } + + public EJDB2Builder noWAL(boolean v) { + this.no_wal = v; + return this; + } + + public EJDB2Builder withWAL() { + this.no_wal = false; + return this; + } + + public EJDB2Builder sortBufferSize(int v) { + this.sort_buffer_sz = v; + return this; + } + + public EJDB2Builder documentBufferSize(int v) { + this.document_buffer_sz = v; + return this; + } + + public EJDB2Builder truncate() { + iwkv.truncate(); + return this; + } + + public EJDB2Builder readonly() { + iwkv.readonly(); + return this; + } + + public EJDB2Builder fileLockFailFast(boolean v) { + iwkv.fileLockFailFast(v); + return this; + } + + public EJDB2Builder randomSeed(long seed) { + iwkv.randomSeed(seed); + return this; + } + + public EJDB2Builder walCRCOnCheckpoint(boolean v) { + iwkv.walCRCOnCheckpoint(v); + return this; + } + + public EJDB2Builder walSavepointTimeoutSec(int v) { + iwkv.walSavepointTimeoutSec(v); + return this; + } + + public EJDB2Builder walCheckpointTimeoutSec(int v) { + iwkv.walCheckpointTimeoutSec(v); + return this; + } + + public EJDB2Builder walBufferSize(long v) { + iwkv.walBufferSize(v); + return this; + } + + public EJDB2Builder walCheckpointBufferSize(long v) { + iwkv.walCheckpointBufferSize(v); + return this; + } + + public EJDB2Builder httpEnabled(boolean v) { + http.enabled = v; + return this; + } + + public EJDB2Builder httpPort(int v) { + http.port = v; + return this; + } + + public EJDB2Builder httpBind(String v) { + http.bind = v; + return this; + } + + public EJDB2Builder httpAccessToken(String v) { + http.access_token = v; + return this; + } + + public EJDB2Builder httpReadAnon(boolean v) { + http.read_anon = v; + return this; + } + + public EJDB2Builder httpMaxBodySize(int v) { + http.max_body_size = v; + return this; + } + + public EJDB2 open() { + return new EJDB2(this); + } + + @Override + public String toString() { + return new StringJoiner(", ", EJDB2Builder.class.getSimpleName() + "[", "]").add("no_wal=" + no_wal) + .add("sort_buffer_sz=" + sort_buffer_sz).add("document_buffer_sz=" + document_buffer_sz).add("iwkv=" + iwkv) + .add("http=" + http).toString(); + } + + public static class EJDB2HttpOptions implements Serializable { + + private static final long serialVersionUID = 5957416367411081107L; + + boolean enabled; + int port; + String bind; + String access_token; + boolean read_anon; + int max_body_size; + + public boolean isEnabled() { + return enabled; + } + + public int getPort() { + return port; + } + + public String getBind() { + return bind; + } + + public String getAccessToken() { + return access_token; + } + + boolean getReadAnon() { + return read_anon; + } + + int getMaxBodySize() { + return max_body_size; + } + + @Override + public String toString() { + return new StringJoiner(", ", EJDB2HttpOptions.class.getSimpleName() + "[", "]").add("enabled=" + enabled) + .add("port=" + port).add("bind='" + bind + "'").add("access_token='" + access_token + "'") + .add("read_anon=" + read_anon).add("max_body_size=" + max_body_size).toString(); + } + } +} diff --git a/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/EJDB2Document.java b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/EJDB2Document.java new file mode 100644 index 0000000..faa1b3f --- /dev/null +++ b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/EJDB2Document.java @@ -0,0 +1,36 @@ +package com.softmotions.ejdb2; + +import com.softmotions.ejdb2.JSON.ValueType; + +public final class EJDB2Document { + + public final long id; + + public final JSON json; + + EJDB2Document(long id, byte[] data) { + this(id, new JSON(data)); + } + + EJDB2Document(long id, String data) { + this(id, new JSON(data)); + } + + EJDB2Document(long id, JSON json) { + if (json.type != ValueType.OBJECT && json.type != ValueType.NULL) { + throw new IllegalArgumentException("json is not an object or null"); + } + this.id = id; + this.json = json; + } + + @Override + public String toString() { + return "[" + id + ' ' + json.toString() + ']'; + } + + @Override + public int hashCode() { + return json.hashCode(); + } +} diff --git a/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/EJDB2DocumentCallback.java b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/EJDB2DocumentCallback.java new file mode 100644 index 0000000..f672519 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/EJDB2DocumentCallback.java @@ -0,0 +1,27 @@ +package com.softmotions.ejdb2; + +/** + * SAM callback used iterate over query result set. + */ +public interface EJDB2DocumentCallback { + + /** + * Called on every JSON record in result set. + * + * Implementor can control iteration behavior by returning a step getting next + * record: + * + *

    + *
  • {@code 1} go to the next record
  • + *
  • {@code N} move forward by {@code N} records
  • + *
  • {@code -1} iterate current record again
  • + *
  • {@code -2} go to the previous record
  • + *
  • {@code 0} stop iteration
  • + *
+ * + * @param id Current document identifier + * @param json Document {@link JSON} body as string + * @return Number of records to skip + */ + long onDocument(EJDB2Document doc); +} diff --git a/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/EJDB2Exception.java b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/EJDB2Exception.java new file mode 100644 index 0000000..8d2b6b9 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/EJDB2Exception.java @@ -0,0 +1,33 @@ +package com.softmotions.ejdb2; + +/** + * @author Adamansky Anton (adamansky@softmotions.com) + */ +public final class EJDB2Exception extends RuntimeException { + + private static final long serialVersionUID = 2380289380319769542L; + + private final long code; + + private final long errno; + + public long getCode() { + return code; + } + + public long getErrno() { + return errno; + } + + public EJDB2Exception(long code, long errno, String message, Throwable cause) { + super("@ejdb IWRC:" + code + " errno:" + errno + ' ' + message, cause); + this.code = code; + this.errno = errno; + } + + public EJDB2Exception(long code, long errno, String message) { + super("@ejdb IWRC:" + code + " errno:" + errno + ' ' + message); + this.code = code; + this.errno = errno; + } +} diff --git a/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/IWKVOptions.java b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/IWKVOptions.java new file mode 100644 index 0000000..def6808 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/IWKVOptions.java @@ -0,0 +1,141 @@ +package com.softmotions.ejdb2; + +import java.io.Serializable; +import java.util.StringJoiner; + +/** + * @author Adamansky Anton (adamansky@softmotions.com) + */ +public final class IWKVOptions implements Serializable { + + private static final long serialVersionUID = -6138184402489045096L; + + private final String path; + + private long oflags; + + private long random_seed; + + private boolean file_lock_fail_fast = true; + + private final WALOptions wal = new WALOptions(); + + public String getPath() { + return path; + } + + public boolean isTruncate() { + return (oflags & 0x4) != 0; + } + + public boolean isReadOnly() { + return (oflags & 0x4) != 0; + } + + public long getRandomSeed() { + return random_seed; + } + + public boolean getFileLockFailFast() { + return file_lock_fail_fast; + } + + public WALOptions getWALOptions() { + return this.wal; + } + + public IWKVOptions(String path) { + this.path = path; + } + + IWKVOptions truncate() { + oflags |= 0x4; + return this; + } + + IWKVOptions readonly() { + oflags |= 0x2; + return this; + } + + IWKVOptions fileLockFailFast(boolean v) { + this.file_lock_fail_fast = v; + return this; + } + + IWKVOptions randomSeed(long seed) { + this.random_seed = seed; + return this; + } + + IWKVOptions walCRCOnCheckpoint(boolean v) { + wal.check_crc_on_checkpoint = v; + return this; + } + + IWKVOptions walSavepointTimeoutSec(int v) { + wal.savepoint_timeout_sec = v; + return this; + } + + IWKVOptions walCheckpointTimeoutSec(int v) { + wal.checkpoint_timeout_sec = v; + return this; + } + + IWKVOptions walBufferSize(long v) { + wal.buffer_sz = v; + return this; + } + + IWKVOptions walCheckpointBufferSize(long v) { + wal.checkpoint_buffer_sz = v; + return this; + } + + @Override + public String toString() { + return new StringJoiner(", ", IWKVOptions.class.getSimpleName() + "[", "]").add("path='" + path + "'") + .add("oflags=" + oflags).add("file_lock_fail_fast=" + file_lock_fail_fast).add("random_seed=" + random_seed) + .add("wal=" + wal).toString(); + } + + public static final class WALOptions implements Serializable { + + private static final long serialVersionUID = 2406233154956721582L; + + private boolean check_crc_on_checkpoint; + private int savepoint_timeout_sec; + private int checkpoint_timeout_sec; + private long buffer_sz; + private long checkpoint_buffer_sz; + + public boolean getCheckCRCOnCheckpoint() { + return this.check_crc_on_checkpoint; + } + + public int getSavePointTimeoutSec() { + return this.savepoint_timeout_sec; + } + + public int getCheckPointTimeoutSec() { + return this.checkpoint_timeout_sec; + } + + public long getBufferSize() { + return this.buffer_sz; + } + + public long getCheckpointBufferSize() { + return this.checkpoint_buffer_sz; + } + + @Override + public String toString() { + return new StringJoiner(", ", WALOptions.class.getSimpleName() + "[", "]") + .add("check_crc_on_checkpoint=" + check_crc_on_checkpoint) + .add("savepoint_timeout_sec=" + savepoint_timeout_sec).add("checkpoint_timeout_sec=" + checkpoint_timeout_sec) + .add("buffer_sz=" + buffer_sz).add("checkpoint_buffer_sz=" + checkpoint_buffer_sz).toString(); + } + } +} diff --git a/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/JQL.java b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/JQL.java new file mode 100644 index 0000000..ce3b58a --- /dev/null +++ b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/JQL.java @@ -0,0 +1,459 @@ +package com.softmotions.ejdb2; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import java.util.concurrent.ConcurrentHashMap; + +/** + * EJDB2 Query specification. + *

+ * Query can be reused multiple times with various placeholder parameters. See + * JQL specification: + * https://github.com/Softmotions/ejdb/blob/master/README.md#jql + *

+ * Memory resources used by JQL instance must be released explicitly by + * {@link JQL#close()}. + *

+ * Note: If user did not close instance explicitly it will be + * freed anyway once jql object will be garbage collected. + *

+ * Typical usage: + * + *

+ * {@code
+ *    try (JQL q = db.createQuery("/[foo=:val]", "mycoll")
+ *                   .setString("val", "bar")) {
+ *       q.execute((docId, doc) -> {
+ *         System.out.println(String.format("Found %d %s", docId, doc));
+ *         return 1;
+ *       });
+ *    }
+ * }
+ * 
+ */ +public final class JQL implements AutoCloseable { + + private static final ReferenceQueue refQueue = new ReferenceQueue<>(); + + @SuppressWarnings("StaticCollection") + private static final Map refs = new ConcurrentHashMap(); + + private static final Thread cleanupThread = new Thread(() -> { + while (true) { + try { + ((Reference) refQueue.remove()).cleanup(); + } catch (InterruptedException ignored) { + } + } + }); + + static { + cleanupThread.setDaemon(true); + cleanupThread.start(); + } + + private final EJDB2 db; + + private final String query; + + private String collection; + + private long skip; + + private long limit; + + private long _handle; + + private ByteArrayOutputStream explain; + + /** + * Owner database instance + */ + public EJDB2 getDb() { + return db; + } + + /** + * Query specification used to construct this query object. + */ + public String getQuery() { + return query; + } + + /** + * Collection name used for this query + */ + public String getCollection() { + return collection; + } + + public JQL setCollection(String collection) { + this.collection = collection; + return this; + } + + /** + * Turn on collecting of query execution log + * + * @see #getExplainLog() + */ + public JQL withExplain() { + explain = new ByteArrayOutputStream(); + return this; + } + + /** + * Turn off collecting of query execution log + * + * @see #getExplainLog() + */ + public JQL withNoExplain() { + explain = null; + return this; + } + + public String getExplainLog() { + try { + return explain != null ? explain.toString("UTF-8") : null; + } catch (UnsupportedEncodingException ignored) { + return null; + } + } + + /** + * Number of records to skip. This parameter takes precedence over {@code skip} + * encoded in query spec. + * + * @return + */ + public JQL setSkip(long skip) { + this.skip = skip; + return this; + } + + public long getSkip() { + return skip > 0 ? skip : _get_skip(); + } + + /** + * Maximum number of records to retrive. This parameter takes precedence over + * {@code limit} encoded in query spec. + */ + public JQL setLimit(long limit) { + this.limit = limit; + return this; + } + + public long getLimit() { + return limit > 0 ? limit : _get_limit(); + } + + /** + * Set positional string parameter starting for {@code 0} index. + *

+ * Example: + * + *

+   * {@code
+   *  db.createQuery("/[foo=:?]", "mycoll").setString(0, "zaz")
+   * }
+   * 
+ * + * @param pos Zero based positional index + * @param val Value to set + * @return + * @throws EJDB2Exception + */ + public JQL setString(int pos, String val) throws EJDB2Exception { + _set_string(pos, null, val, 0); + return this; + } + + /** + * Set string parameter placeholder in query spec. + *

+ * Example: + * + *

+   * {@code
+   *  db.createQuery("/[foo=:val]", "mycoll").setString("val", "zaz");
+   * }
+   * 
+ * + * @param placeholder Placeholder name + * @param val Value to set + * @return + * @throws EJDB2Exception + */ + public JQL setString(String placeholder, String val) throws EJDB2Exception { + _set_string(0, placeholder, val, 0); + return this; + } + + public JQL setLong(int pos, long val) throws EJDB2Exception { + _set_long(pos, null, val); + return this; + } + + public JQL setLong(String placeholder, long val) throws EJDB2Exception { + _set_long(0, placeholder, val); + return this; + } + + public JQL setJSON(int pos, String json) throws EJDB2Exception { + _set_string(pos, null, json, 1); + return this; + } + + public JQL setJSON(int pos, JSON json) throws EJDB2Exception { + _set_string(pos, null, json.toString(), 1); + return this; + } + + public JQL setJSON(String placeholder, String json) throws EJDB2Exception { + _set_string(0, placeholder, json, 1); + return this; + } + + public JQL setJSON(String placeholder, JSON json) throws EJDB2Exception { + _set_string(0, placeholder, json.toString(), 1); + return this; + } + + public JQL setRegexp(int pos, String regexp) throws EJDB2Exception { + _set_string(pos, null, regexp, 2); + return this; + } + + public JQL setRegexp(String placeholder, String regexp) throws EJDB2Exception { + _set_string(0, placeholder, regexp, 2); + return this; + } + + public JQL setDouble(int pos, double val) throws EJDB2Exception { + _set_double(pos, null, val); + return this; + } + + public JQL setDouble(String placeholder, double val) throws EJDB2Exception { + _set_double(0, placeholder, val); + return this; + } + + public JQL setBoolean(int pos, boolean val) throws EJDB2Exception { + _set_boolean(pos, null, val); + return this; + } + + public JQL setBoolean(String placeholder, boolean val) throws EJDB2Exception { + _set_boolean(0, placeholder, val); + return this; + } + + public JQL setNull(int pos) throws EJDB2Exception { + _set_null(pos, null); + return this; + } + + public JQL setNull(String placeholder) throws EJDB2Exception { + _set_null(0, placeholder); + return this; + } + + /** + * Execute query and handle record {@link EJDB2Document} values by provided + * {@code cb} + * + * @param cb Optional callback + * @throws EJDB2Exception + */ + public void execute(EJDB2DocumentCallback cb) throws EJDB2Exception { + if (explain != null) { + explain.reset(); + } + if (cb != null) { + _execute(db, (id, sv) -> cb.onDocument(new EJDB2Document(id, sv)), explain); + } else { + _execute(db, null, explain); + } + } + + /** + * Execute query without result set callback. + * + * @throws EJDB2Exception + */ + public void execute() throws EJDB2Exception { + execute(null); + } + + public void executeRaw(JQLCallback cb) throws EJDB2Exception { + if (explain != null) { + explain.reset(); + } + if (cb != null) { + _execute(db, (id, sv) -> cb.onRecord(id, sv), explain); + } else { + _execute(db, null, explain); + } + } + + public List list() throws EJDB2Exception { + List list = new ArrayList<>(); + execute((doc) -> { + list.add(doc); + return 1; + }); + return list; + } + + public EJDB2Document first() { + final EJDB2Document[] v = { null }; + if (explain != null) { + explain.reset(); + } + _execute(db, (id, json) -> { + v[0] = new EJDB2Document(id, json); + return 0; + }, explain); + return v[0]; + } + + /** + * Get first document body as JSON string or null. + */ + public String firstValue() { + final String[] v = { null }; + if (explain != null) { + explain.reset(); + } + _execute(db, (id, json) -> { + v[0] = json; + return 0; + }, explain); + return v[0]; + } + + /** + * Get first document id ot null + */ + public Long firstId() { + final Long[] v = { null }; + if (explain != null) { + explain.reset(); + } + _execute(db, (id, json) -> { + v[0] = id; + return 0; + }, explain); + return v[0]; + } + + /** + * Execute scalar query. + *

+ * Example: + * + *

+   * long count = db.createQuery("@mycoll/* | count").executeScalarInt();
+   * 
+ */ + public long executeScalarInt() { + if (explain != null) { + explain.reset(); + } + return _execute_scalar_long(db, explain); + } + + /** + * Reset data stored in positional placeholderss + */ + public void reset() { + if (explain != null) { + explain.reset(); + } + _reset(); + } + + /** + * Close query instance releasing memory resources + */ + @Override + public void close() throws Exception { + Reference ref = refs.get(_handle); + if (ref != null) { + ref.enqueue(); + } else { + long h = _handle; + if (h != 0) { + _destroy(h); + } + } + } + + JQL(EJDB2 db, String query, String collection) throws EJDB2Exception { + this.db = db; + this.query = query; + this.collection = collection; + _init(db, query, collection); + // noinspection InstanceVariableUsedBeforeInitialized + refs.put(_handle, new Reference(this, refQueue)); + } + + @Override + public String toString() { + return new StringJoiner(", ", JQL.class.getSimpleName() + "[", "]") + .add("query=" + query) + .add("collection=" + collection) + .toString(); + } + + private static class Reference extends WeakReference { + private long handle; + + Reference(JQL jql, ReferenceQueue rq) { + super(jql, rq); + handle = jql._handle; + } + + void cleanup() { + long h = handle; + handle = 0L; + if (h != 0) { + refs.remove(h); + _destroy(h); + } + } + } + + private static native void _destroy(long handle); + + private native void _init(EJDB2 db, String query, String collection); + + private native void _execute(EJDB2 db, JQLCallback cb, OutputStream explainLog); + + private native long _execute_scalar_long(EJDB2 db, OutputStream explainLog); + + private native void _reset(); + + private native long _get_limit(); + + private native long _get_skip(); + + private native void _set_string(int pos, String placeholder, String val, int type); + + private native void _set_long(int pos, String placeholder, long val); + + private native void _set_double(int pos, String placeholder, double val); + + private native void _set_boolean(int pos, String placeholder, boolean val); + + private native void _set_null(int pos, String placeholder); +} \ No newline at end of file diff --git a/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/JQLCallback.java b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/JQLCallback.java new file mode 100644 index 0000000..ea461a2 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/JQLCallback.java @@ -0,0 +1,27 @@ +package com.softmotions.ejdb2; + +/** + * SAM callback used iterate over query result set. + */ +public interface JQLCallback { + + /** + * Called on every JSON record in result set. + * + * Implementor can control iteration behavior by returning a step getting next + * record: + * + *
    + *
  • {@code 1} go to the next record
  • + *
  • {@code N} move forward by {@code N} records
  • + *
  • {@code -1} iterate current record again
  • + *
  • {@code -2} go to the previous record
  • + *
  • {@code 0} stop iteration
  • + *
+ * + * @param id Current document identifier + * @param json Document JSOn body as string + * @return Number of records to skip + */ + long onRecord(long id, String json); +} \ No newline at end of file diff --git a/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/JSON.java b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/JSON.java new file mode 100644 index 0000000..534fb7f --- /dev/null +++ b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/JSON.java @@ -0,0 +1,1234 @@ +package com.softmotions.ejdb2; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; + +/** + * JSON parser/container. + * + * Based on: + * + * - https://github.com/json-iterator (MIT) + * + * - https://github.com/ralfstx/minimal-json (MIT) + */ +public final class JSON { + + public static ObjectBuilder buildObject() { + return new ObjectBuilder(); + } + + public static ArrayBuilder buildArray() { + return new ArrayBuilder(); + } + + public static JSON fromString(String val) { + return new JSON(val); + } + + public static JSON fromBytes(byte[] bytes) { + return new JSON(bytes); + } + + public static JSON fromMap(Map map) { + return new JSON(ValueType.OBJECT, map); + } + + public static JSON fromList(List list) { + return new JSON(ValueType.ARRAY, list); + } + + private static final ValueType[] valueTypes = new ValueType[256]; + private static final int[] hexDigits = new int['f' + 1]; + private static JSON UNKNOWN = new JSON(ValueType.UNKNOWN, null); + + static { + for (int i = 0; i < valueTypes.length; ++i) { + valueTypes[i] = ValueType.UNKNOWN; + } + valueTypes['"'] = ValueType.STRING; + valueTypes['-'] = ValueType.NUMBER; + valueTypes['0'] = ValueType.NUMBER; + valueTypes['1'] = ValueType.NUMBER; + valueTypes['2'] = ValueType.NUMBER; + valueTypes['3'] = ValueType.NUMBER; + valueTypes['4'] = ValueType.NUMBER; + valueTypes['5'] = ValueType.NUMBER; + valueTypes['6'] = ValueType.NUMBER; + valueTypes['7'] = ValueType.NUMBER; + valueTypes['8'] = ValueType.NUMBER; + valueTypes['9'] = ValueType.NUMBER; + valueTypes['t'] = ValueType.BOOLEAN; + valueTypes['f'] = ValueType.BOOLEAN; + valueTypes['n'] = ValueType.NULL; + valueTypes['['] = ValueType.ARRAY; + valueTypes['{'] = ValueType.OBJECT; + } + + static { + for (int i = 0; i < hexDigits.length; ++i) { + hexDigits[i] = -1; + } + for (int i = '0'; i <= '9'; ++i) { + hexDigits[i] = (i - '0'); + } + for (int i = 'a'; i <= 'f'; ++i) { + hexDigits[i] = ((i - 'a') + 10); + } + for (int i = 'A'; i <= 'F'; ++i) { + hexDigits[i] = ((i - 'A') + 10); + } + } + + public final Object value; + public final ValueType type; + + private char[] reusableChars = new char[32]; + private byte[] buf = new byte[0]; + private int head; + private int tail; + + JSON(byte[] buf) { + this.buf = buf; + tail = buf.length; + type = whatIsNext(); + value = read(type); + reset(); + } + + JSON(String data) { + this(data.getBytes()); + } + + private JSON(ValueType type, Object value) { + this.type = type; + this.value = value; + } + + public void write(Writer w) { + if (type != ValueType.UNKNOWN) { + writeTo(w, value); + } + } + + @Override + public String toString() { + StringWriter sw = new StringWriter(); + write(sw); + return sw.toString(); + } + + public boolean isUnknown() { + return type == ValueType.UNKNOWN; + } + + public boolean isNull() { + return type == ValueType.NULL; + } + + public boolean isNumber() { + return type == ValueType.NUMBER; + } + + public boolean isBoolean() { + return type == ValueType.BOOLEAN; + } + + public boolean isString() { + return type == ValueType.STRING; + } + + public boolean isObject() { + return type == ValueType.OBJECT; + } + + public boolean isArray() { + return type == ValueType.ARRAY; + } + + public Builder modify() { + return new Builder(this); + } + + public JSON get(String key) { + if (type == ValueType.ARRAY) { + try { + return get(Integer.parseInt(key)); + } catch (NumberFormatException ignored) { + return UNKNOWN; + } + } + if (type != ValueType.OBJECT) { + return UNKNOWN; + } + Object v = ((Map) value).get(key); + return new JSON(ValueType.getTypeOf(v), v); + } + + public JSON get(int index) { + if (type == ValueType.OBJECT) { + return get(String.valueOf(index)); + } + if (type != ValueType.ARRAY) { + return UNKNOWN; + } + List list = (List) value; + if (index < 0 || index >= list.size()) { + return UNKNOWN; + } + Object v = list.get(index); + return new JSON(ValueType.getTypeOf(v), v); + } + + public T cast() { + return (T) value; + } + + public T orDefault(T defaultValue) { + return type != ValueType.UNKNOWN ? (T) value : defaultValue; + } + + public T orDefaultNotNull(T defaultValue) { + return type != ValueType.UNKNOWN && type != ValueType.NULL ? (T) value : defaultValue; + } + + public String asStringOr(String fallbackValue) { + return type == ValueType.STRING ? (String) value : fallbackValue; + } + + public String asString() { + return asStringOr(null); + } + + public String asTextOr(String fallbackValue) { + if (type == ValueType.UNKNOWN) { + return fallbackValue; + } else { + return String.valueOf(value); + } + } + + public String asText() { + return asTextOr(null); + } + + public Boolean asBooleanOr(Boolean fallbackValue) { + return type == ValueType.BOOLEAN ? (Boolean) value : fallbackValue; + } + + public Boolean asBoolean() { + return asBooleanOr(null); + } + + public Number asNumberOr(Number fallbackValue) { + return type == ValueType.NUMBER ? (Number) value : fallbackValue; + } + + public Number asNumber() { + return asNumberOr(null); + } + + public Integer asIntegerOr(Integer fallbackValue) { + Number n = asNumberOr(fallbackValue); + return n != null ? n.intValue() : null; + } + + public Integer asInteger() { + return asIntegerOr(null); + } + + public Map asMapOr(Map fallbackValue) { + return type == ValueType.OBJECT ? (Map) value : fallbackValue; + } + + public Map asMapOrEmpty() { + return asMapOr(Collections.emptyMap()); + } + + public List asListOr(List fallbackValue) { + return type == ValueType.ARRAY ? (List) value : fallbackValue; + } + + public List asListOrEmpty() { + return asListOr(Collections.emptyList()); + } + + public JSON asKnownJsonOr(JSON fallbackValue) { + return type == ValueType.UNKNOWN ? fallbackValue : this; + } + + public JSON asKnownJson() { + return asKnownJsonOr(null); + } + + public JSON at(String pointer) { + if (type != ValueType.OBJECT) { + return UNKNOWN; + } + if (pointer == null || "/".equals(pointer) || pointer.isEmpty()) { + return this; + } + return traverse(this, createPointer(pointer)); + } + + private JSON traverse(JSON obj, List pp) { + if (pp.isEmpty() || obj.isUnknown()) { + return obj; + } + String key = pp.remove(0); + if (obj.isObject() || obj.isArray()) { + return traverse(obj.get(key), pp); + } else { + return UNKNOWN; + } + } + + private List createPointer(String pointer) { + if (pointer.charAt(0) == '#') { + try { + pointer = URLDecoder.decode(pointer, "UTF_8"); + } catch (UnsupportedEncodingException e) { + throw new JSONException(e); + } + } + if (pointer.isEmpty() || pointer.charAt(0) != '/') { + throw new JSONException("Invalid JSON pointer: " + pointer); + } + String[] parts = pointer.substring(1).split("/"); + ArrayList res = new ArrayList<>(parts.length); + + for (int i = 0, l = parts.length; i < l; ++i) { + while (parts[i].contains("~1")) { + parts[i] = parts[i].replace("~1", "/"); + } + while (parts[i].contains("~0")) { + parts[i] = parts[i].replace("~0", "~"); + } + res.add(parts[i]); + } + return res; + } + + private static void writeTo(Writer w, Object val) { + try { + write(new JsonWriter(w), val); + } catch (IOException e) { + throw new JSONException(e); + } + } + + private static void write(JsonWriter w, Object val) throws IOException { + if (val == null) { + w.writeLiteral("null"); + return; + } + final Class clazz = val.getClass(); + if (clazz == String.class) { + w.writeString((String) val); + return; + } else if (clazz == Boolean.class) { + Boolean bv = (Boolean) val; + w.writeLiteral(bv ? "true" : "false"); + return; + } + if (val instanceof Map) { + final Map map = (Map) val; + Iterator> iter = map.entrySet().iterator(); + w.writeObjectOpen(); + if (iter.hasNext()) { + Entry v = iter.next(); + w.writeMemberName(v.getKey()); + w.writeMemberSeparator(); + write(w, v.getValue()); + while (iter.hasNext()) { + v = iter.next(); + w.writeObjectSeparator(); + w.writeMemberName(v.getKey()); + w.writeMemberSeparator(); + write(w, v.getValue()); + } + } + w.writeObjectClose(); + } else if (val instanceof List) { + final List list = (List) val; + w.writeArrayOpen(); + Iterator iter = list.iterator(); + if (iter.hasNext()) { + write(w, iter.next()); + while (iter.hasNext()) { + w.writeArraySeparator(); + write(w, iter.next()); + } + } + w.writeArrayClose(); + } else if (val instanceof Number) { + w.writeNumber((Number) val); + } else { + w.writeString(val.toString()); + } + } + + private void reset() { + buf = null; + head = 0; + tail = 0; + } + + private Object read(ValueType valueType) { + try { + switch (valueType) { + case STRING: + return readString(); + case NUMBER: + return readNumber(); + case NULL: + head += 4; + return null; + case BOOLEAN: + return readBoolean(); + case ARRAY: + return readArray(new ArrayList(4)); + case OBJECT: + return readObject(new LinkedHashMap(4)); + default: + throw reportError("read", "unexpected value type: " + valueType); + } + } catch (ArrayIndexOutOfBoundsException e) { + throw reportError("read", "premature end"); + } + } + + private Object read() { + return read(whatIsNext()); + } + + private Map readObject(Map map) { + byte c = nextToken(); + if ('{' == c) { + c = nextToken(); + if ('"' == c) { + unreadByte(); + String field = readString(); + if (nextToken() != ':') { + throw reportError("readObject", "expect :"); + } + map.put(field, read()); + while (nextToken() == ',') { + field = readString(); + if (nextToken() != ':') { + throw reportError("readObject", "expect :"); + } + map.put(field, read()); + } + return map; + } + if ('}' == c) { + return map; + } + throw reportError("readObject", "expect \" after {"); + } + if ('n' == c) { + head += 3; + return map; + } + throw reportError("readObject", "expect { or n"); + } + + private ArrayList readArray(ArrayList list) { + byte c = nextToken(); + if (c == '[') { + c = nextToken(); + if (c != ']') { + unreadByte(); + list.add(read()); + while (nextToken() == ',') { + list.add(read()); + } + return list; + } + return list; + } + if (c == 'n') { + return list; + } + throw reportError("readArray", "expect [ or n, but found: " + (char) c); + } + + private boolean readBoolean() { + byte c = nextToken(); + if ('t' == c) { + head += 3; // true + return true; + } + if ('f' == c) { + head += 4; // false + return false; + } + throw reportError("readBoolean", "expect t or f, found: " + c); + } + + private Object readNumber() { + NumberChars numberChars = readNumberImpl(); + String numberStr = new String(numberChars.chars, 0, numberChars.charsLength); + Double number = Double.valueOf(numberStr); + if (numberChars.dotFound) { + return number; + } + double doubleNumber = number; + if (doubleNumber == Math.floor(doubleNumber) && !Double.isInfinite(doubleNumber)) { + long longNumber = Long.valueOf(numberStr); + if (longNumber <= Integer.MAX_VALUE && longNumber >= Integer.MIN_VALUE) { + return (int) longNumber; + } + return longNumber; + } + return number; + } + + private NumberChars readNumberImpl() { + int j = 0; + boolean dotFound = false; + for (;;) { + for (int i = head; i < tail; ++i) { + if (j == reusableChars.length) { + char[] newBuf = new char[reusableChars.length * 2]; + System.arraycopy(reusableChars, 0, newBuf, 0, reusableChars.length); + reusableChars = newBuf; + } + byte c = buf[i]; + switch (c) { + case '.': + case 'e': + case 'E': + dotFound = true; + // fallthrough + case '-': + case '+': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + reusableChars[j++] = (char) c; + break; + default: + head = i; + NumberChars numberChars = new NumberChars(); + numberChars.chars = reusableChars; + numberChars.charsLength = j; + numberChars.dotFound = dotFound; + return numberChars; + } + } + + head = tail; + NumberChars numberChars = new NumberChars(); + numberChars.chars = reusableChars; + numberChars.charsLength = j; + numberChars.dotFound = dotFound; + return numberChars; + } + } + + private String readString() { + byte c = nextToken(); + if (c != '"') { + if (c == 'n') { + head += 3; + return null; + } + throw reportError("readString", "expect string or null, but " + (char) c); + } + int j = parseString(); + return new String(reusableChars, 0, j); + } + + private int parseString() { + byte c; + int i = head; + int bound = reusableChars.length; + for (int j = 0; j < bound; ++j) { + c = buf[i++]; + if (c == '"') { + head = i; + return j; + } + if ((c ^ '\\') < 1) { + break; + } + reusableChars[j] = (char) c; + } + int alreadyCopied = 0; + if (i > head) { + alreadyCopied = i - head - 1; + head = i - 1; + } + return readStringSlowPath(alreadyCopied); + } + + private int translateHex(byte b) { + int val = hexDigits[b]; + if (val == -1) { + throw new IndexOutOfBoundsException(b + " is not valid hex digit"); + } + return val; + } + + private int readStringSlowPath(int j) { + try { + boolean isExpectingLowSurrogate = false; + for (int i = head; i < tail;) { + int bc = buf[i++]; + if (bc == '"') { + head = i; + return j; + } + if (bc == '\\') { + bc = buf[i++]; + switch (bc) { + case 'b': + bc = '\b'; + break; + case 't': + bc = '\t'; + break; + case 'n': + bc = '\n'; + break; + case 'f': + bc = '\f'; + break; + case 'r': + bc = '\r'; + break; + case '"': + case '/': + case '\\': + break; + case 'u': + bc = (translateHex(buf[i++]) << 12) + (translateHex(buf[i++]) << 8) + (translateHex(buf[i++]) << 4) + + translateHex(buf[i++]); + if (Character.isHighSurrogate((char) bc)) { + if (isExpectingLowSurrogate) { + throw new JSONException("invalid surrogate"); + } else { + isExpectingLowSurrogate = true; + } + } else if (Character.isLowSurrogate((char) bc)) { + if (isExpectingLowSurrogate) { + isExpectingLowSurrogate = false; + } else { + throw new JSONException("invalid surrogate"); + } + } else { + if (isExpectingLowSurrogate) { + throw new JSONException("invalid surrogate"); + } + } + break; + + default: + throw reportError("readStringSlowPath", "invalid escape character: " + bc); + } + } else if ((bc & 0x80) != 0) { + final int u2 = buf[i++]; + if ((bc & 0xE0) == 0xC0) { + bc = ((bc & 0x1F) << 6) + (u2 & 0x3F); + } else { + final int u3 = buf[i++]; + if ((bc & 0xF0) == 0xE0) { + bc = ((bc & 0x0F) << 12) + ((u2 & 0x3F) << 6) + (u3 & 0x3F); + } else { + final int u4 = buf[i++]; + if ((bc & 0xF8) == 0xF0) { + bc = ((bc & 0x07) << 18) + ((u2 & 0x3F) << 12) + ((u3 & 0x3F) << 6) + (u4 & 0x3F); + } else { + throw reportError("readStringSlowPath", "invalid unicode character"); + } + if (bc >= 0x10000) { + // check if valid unicode + if (bc >= 0x110000) { + throw reportError("readStringSlowPath", "invalid unicode character"); + } + // split surrogates + final int sup = bc - 0x10000; + if (reusableChars.length == j) { + char[] newBuf = new char[reusableChars.length * 2]; + System.arraycopy(reusableChars, 0, newBuf, 0, reusableChars.length); + reusableChars = newBuf; + } + reusableChars[j++] = (char) ((sup >>> 10) + 0xd800); + if (reusableChars.length == j) { + char[] newBuf = new char[reusableChars.length * 2]; + System.arraycopy(reusableChars, 0, newBuf, 0, reusableChars.length); + reusableChars = newBuf; + } + reusableChars[j++] = (char) ((sup & 0x3ff) + 0xdc00); + continue; + } + } + } + } + if (reusableChars.length == j) { + char[] newBuf = new char[reusableChars.length * 2]; + System.arraycopy(reusableChars, 0, newBuf, 0, reusableChars.length); + reusableChars = newBuf; + } + reusableChars[j++] = (char) bc; + } + throw reportError("readStringSlowPath", "incomplete string"); + } catch (IndexOutOfBoundsException e) { + throw reportError("readString", "incomplete string"); + } + } + + private ValueType whatIsNext() { + ValueType valueType = valueTypes[nextToken()]; + unreadByte(); + return valueType; + } + + private void unreadByte() { + if (head == 0) { + throw reportError("unreadByte", "unread too many bytes"); + } + head--; + } + + private byte nextToken() { + int i = head; + for (;;) { + byte c = buf[i++]; + switch (c) { + case ' ': + case '\n': + case '\r': + case '\t': + continue; + default: + head = i; + return c; + } + } + } + + private JSONException reportError(String op, String msg) { + int peekStart = head - 10; + if (peekStart < 0) { + peekStart = 0; + } + int peekSize = head - peekStart; + if (head > tail) { + peekSize = tail - peekStart; + } + String peek = new String(buf, peekStart, peekSize); + throw new JSONException(op + ": " + msg + ", head: " + head + ", peek: " + peek + ", buf: " + new String(buf)); + } + + @Override + public int hashCode() { + return Objects.hash(type, value); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + JSON other = (JSON) obj; + return type == other.type && Objects.equals(value, other.value); + } + + public static enum ValueType { + UNKNOWN, STRING, NUMBER, NULL, BOOLEAN, ARRAY, OBJECT; + + static ValueType getTypeOf(Object v) { + if (v == null) { + return NULL; + } + Class clazz = v.getClass(); + if (clazz == String.class) { + return STRING; + } + if (clazz == Boolean.class) { + return BOOLEAN; + } + if (v instanceof Number) { + return NUMBER; + } + if (v instanceof Map) { + return OBJECT; + } + if (v instanceof List) { + return ARRAY; + } + return UNKNOWN; + } + } + + private static final class NumberChars { + char[] chars; + int charsLength; + boolean dotFound; + } + + private static final class JsonWriter { + private static final char[] QUOT_CHARS = { '\\', '"' }; + private static final char[] BS_CHARS = { '\\', '\\' }; + private static final char[] LF_CHARS = { '\\', 'n' }; + private static final char[] CR_CHARS = { '\\', 'r' }; + private static final char[] TAB_CHARS = { '\\', 't' }; + + // In JavaScript, U+2028 and U+2029 characters count as line endings and must be + // encoded. + // http://stackoverflow.com/questions/2965293/javascript-parse-error-on-u2028-unicode-character + private static final char[] UNICODE_2028_CHARS = { '\\', 'u', '2', '0', '2', '8' }; + private static final char[] UNICODE_2029_CHARS = { '\\', 'u', '2', '0', '2', '9' }; + private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', + 'e', 'f' }; + + private final Writer writer; + + JsonWriter(Writer writer) { + this.writer = writer; + } + + void writeLiteral(String value) throws IOException { + writer.write(value); + } + + void writeNumber(Number value) throws IOException { + String s = value.toString(); + if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) { + while (s.endsWith("0")) { + s = s.substring(0, s.length() - 1); + } + if (s.endsWith(".")) { + s = s.substring(0, s.length() - 1); + } + } + writer.write(s); + } + + void writeString(String string) throws IOException { + writer.write('"'); + writeJsonString(string); + writer.write('"'); + } + + void writeArrayOpen() throws IOException { + writer.write('['); + } + + void writeArrayClose() throws IOException { + writer.write(']'); + } + + void writeArraySeparator() throws IOException { + writer.write(','); + } + + void writeObjectOpen() throws IOException { + writer.write('{'); + } + + void writeObjectClose() throws IOException { + writer.write('}'); + } + + void writeMemberName(String name) throws IOException { + writer.write('"'); + writeJsonString(name); + writer.write('"'); + } + + void writeMemberSeparator() throws IOException { + writer.write(':'); + } + + void writeObjectSeparator() throws IOException { + writer.write(','); + } + + void writeJsonString(String string) throws IOException { + int length = string.length(); + int start = 0; + for (int index = 0; index < length; ++index) { + char[] replacement = getReplacementChars(string.charAt(index)); + if (replacement != null) { + writer.write(string, start, index - start); + writer.write(replacement); + start = index + 1; + } + } + writer.write(string, start, length - start); + } + + static char[] getReplacementChars(char ch) { + if (ch > '\\') { + if (ch < '\u2028' || ch > '\u2029') { + // The lower range contains 'a' .. 'z'. Only 2 checks required. + return null; + } + return ch == '\u2028' ? UNICODE_2028_CHARS : UNICODE_2029_CHARS; + } + if (ch == '\\') { + return BS_CHARS; + } + if (ch > '"') { + // This range contains '0' .. '9' and 'A' .. 'Z'. Need 3 checks to get here. + return null; + } + if (ch == '"') { + return QUOT_CHARS; + } + if (ch > 0x001f) { // CONTROL_CHARACTERS_END + return null; + } + if (ch == '\n') { + return LF_CHARS; + } + if (ch == '\r') { + return CR_CHARS; + } + if (ch == '\t') { + return TAB_CHARS; + } + return new char[] { '\\', 'u', '0', '0', HEX_DIGITS[ch >> 4 & 0x000f], HEX_DIGITS[ch & 0x000f] }; + } + } + + public static final class Builder { + final JSON json; + final ObjectBuilder o; + final ArrayBuilder a; + + Builder(JSON json) { + this.json = json; + if (json.type == ValueType.OBJECT) { + o = new ObjectBuilder((Map) json.value); + a = null; + } else if (json.type == ValueType.ARRAY) { + a = new ArrayBuilder((List) json.value); + o = null; + } else { + throw new JSONException("JSON value must be either Object or Array"); + } + } + + public ObjectBuilder addObject() { + return getA().addObject(); + } + + public ArrayBuilder addObject(JSON val) { + return getA().addObject(val); + } + + public ArrayBuilder addArray() { + return getA().addArray(); + } + + public ArrayBuilder addArray(JSON val) { + return getA().addArray(val); + } + + public ArrayBuilder add(String val) { + return getA().add(val); + } + + public ArrayBuilder add(Number val) { + return getA().add(val); + } + + public ArrayBuilder add(Boolean val) { + return getA().add(val); + } + + public ArrayBuilder addNull() { + return getA().addNull(); + } + + public int length() { + return getA().length(); + } + + public Object get(int index) { + return getA().get(index); + } + + public ArrayBuilder delete(int index) { + return getA().delete(index); + } + + public ObjectBuilder putObject(String key) { + return getO().putObject(key); + } + + public ObjectBuilder putObject(String key, JSON val) { + return getO().putObject(key, val); + } + + public ArrayBuilder putArray(String key) { + return getO().putArray(key); + } + + public ObjectBuilder putArray(String key, JSON val) { + return getO().putArray(key, val); + } + + public ObjectBuilder put(String key, String val) { + return getO().put(key, val); + } + + public ObjectBuilder put(String key, Number val) { + return getO().put(key, val); + } + + public ObjectBuilder put(String key, Boolean val) { + return getO().put(key, val); + } + + public ObjectBuilder putNull(String key) { + return getO().putNull(key); + } + + public ObjectBuilder delete(String key) { + return getO().delete(key); + } + + public Iterable keys() { + return getO().keys(); + } + + public JSON toJSON() { + return json; + } + + @Override + public String toString() { + return json.toString(); + } + + private ArrayBuilder getA() { + if (a == null) { + throw new JSONException("JSON value is not an Array"); + } + return a; + } + + private ObjectBuilder getO() { + if (o == null) { + throw new JSONException("JSON value is not an Object"); + } + return o; + } + } + + public static final class ArrayBuilder { + + final List value; + + private JSON json; + + ArrayBuilder(List value) { + this.value = value; + } + + ArrayBuilder() { + this(new ArrayList<>()); + } + + public ObjectBuilder addObject() { + ObjectBuilder b = new ObjectBuilder(); + value.add(b.value); + return b; + } + + public ArrayBuilder addObject(JSON val) { + if (val.type == ValueType.NULL) { + return addNull(); + } else if (val.type != ValueType.OBJECT) { + throw new JSONException("Value must be an Object"); + } + value.add(val.value); + return this; + } + + public ArrayBuilder addArray() { + ArrayBuilder b = new ArrayBuilder(); + value.add(b.value); + return b; + } + + public ArrayBuilder addArray(JSON val) { + if (val.type == ValueType.NULL) { + return addNull(); + } else if (val.type != ValueType.ARRAY) { + throw new JSONException("Value must be an Array"); + } + value.add(val.value); + return this; + } + + public ArrayBuilder add(String val) { + value.add(val); + return this; + } + + public ArrayBuilder add(Number val) { + value.add(val); + return this; + } + + public ArrayBuilder add(Boolean val) { + value.add(val); + return this; + } + + public ArrayBuilder addNull() { + value.add(null); + return this; + } + + public int length() { + return value.size(); + } + + public Object get(int index) { + return index >= 0 && value.size() > index ? value.get(index) : null; + } + + public ArrayBuilder delete(int index) { + value.remove(index); + return this; + } + + public JSON toJSON() { + if (json == null) { + json = new JSON(ValueType.ARRAY, value); + } + return json; + } + + @Override + public String toString() { + StringWriter sw = new StringWriter(); + writeTo(sw, value); + return sw.toString(); + } + } + + public static final class ObjectBuilder { + + final Map value; + + private JSON json; + + ObjectBuilder(Map value) { + this.value = value; + } + + ObjectBuilder() { + this(new LinkedHashMap<>()); + } + + public ObjectBuilder putObject(String key) { + ObjectBuilder b = new ObjectBuilder(); + value.put(key, b.value); + return b; + } + + public ObjectBuilder putObject(String key, JSON val) { + if (val.type == ValueType.NULL) { + return putNull(key); + } + if (val.type != ValueType.OBJECT) { + throw new JSONException("Value must be an Object"); + } + value.put(key, val.value); + return this; + } + + public ArrayBuilder putArray(String key) { + ArrayBuilder b = new ArrayBuilder(); + value.put(key, b.value); + return b; + } + + public ObjectBuilder putArray(String key, JSON val) { + if (val.type == ValueType.NULL) { + return putNull(key); + } + if (val.type != ValueType.ARRAY) { + throw new JSONException("Value must be an Array"); + } + value.put(key, val.value); + return this; + } + + public ObjectBuilder put(String key, String val) { + value.put(key, val); + return this; + } + + public ObjectBuilder put(String key, Number val) { + value.put(key, val); + return this; + } + + public ObjectBuilder put(String key, Boolean val) { + value.put(key, val); + return this; + } + + public ObjectBuilder putNull(String key) { + value.put(key, null); + return this; + } + + public ObjectBuilder delete(String key) { + value.remove(key); + return this; + } + + public Iterable keys() { + return value.keySet(); + } + + public JSON toJSON() { + if (json == null) { + json = new JSON(ValueType.OBJECT, value); + } + return json; + } + + @Override + public String toString() { + StringWriter sw = new StringWriter(); + writeTo(sw, value); + return sw.toString(); + } + } +} diff --git a/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/JSONException.java b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/JSONException.java new file mode 100644 index 0000000..5969e3a --- /dev/null +++ b/src/bindings/ejdb2_jni/src/main/java/com/softmotions/ejdb2/JSONException.java @@ -0,0 +1,21 @@ +package com.softmotions.ejdb2; + +public class JSONException extends RuntimeException { + + private static final long serialVersionUID = -1180877018017230756L; + + public JSONException() { + } + + public JSONException(String message) { + super(message); + } + + public JSONException(String message, Throwable cause) { + super(message, cause); + } + + public JSONException(Throwable cause) { + super(cause); + } +} diff --git a/src/bindings/ejdb2_jni/src/maven-deploy.sh b/src/bindings/ejdb2_jni/src/maven-deploy.sh new file mode 100755 index 0000000..4ab7a24 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/maven-deploy.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +set -e + +SCRIPTPATH="$( + cd "$(dirname "$0")" + pwd -P +)" +cd $SCRIPTPATH + +VERSION="@EJDB2_JNI_VERSION@" +DEPLOY_JAR_FILE="@DEPLOY_JAR_FILE@" +DEPLOY_SOURCES_JAR_FILE="@SOURCES_JAR_FILE@" + +mvn org.apache.maven.plugins:maven-deploy-plugin:3.0.0-M1:deploy-file \ + -DgroupId=softmotions \ + -DartifactId=ejdb2 \ + -Dversion=${VERSION} \ + -Dpackaging=jar \ + -Dfile=${DEPLOY_JAR_FILE} \ + -Dsources=${DEPLOY_SOURCES_JAR_FILE} \ + -DrepositoryId=repsy_repository \ + -DgeneratePom=true \ + -Durl=https://repo.repsy.io/mvn/adamansky/softmotions diff --git a/src/bindings/ejdb2_jni/src/test/java/com/softmotions/ejdb2/TestEJDB2.java b/src/bindings/ejdb2_jni/src/test/java/com/softmotions/ejdb2/TestEJDB2.java new file mode 100644 index 0000000..dc87793 --- /dev/null +++ b/src/bindings/ejdb2_jni/src/test/java/com/softmotions/ejdb2/TestEJDB2.java @@ -0,0 +1,183 @@ +package com.softmotions.ejdb2; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +import com.softmotions.ejdb2.JSON.ObjectBuilder; +import com.softmotions.ejdb2.JSON.ValueType; + +/** + * @author Adamansky Anton (adamansky@softmotions.com) + */ +public class TestEJDB2 { + + private TestEJDB2() { + } + + private static void jsonBasicTest() throws Exception { + JSON json = JSON.fromString("{\"foo\":\"bar\"}"); + assert json.type == ValueType.OBJECT; + assert json.value != null; + Map map = (Map) json.value; + assert map.get("foo").equals("bar"); + + ObjectBuilder b = JSON.buildObject(); + b.put("foo", "bar").putArray("baz").add(1).add("one").toJSON(); + + json = b.toJSON().at("/baz/1"); + assert json.isString(); + assert "one".equals(json.value); + } + + private static void dbTest() throws Exception { + try (EJDB2 db = new EJDB2Builder("test.db").truncate().withWAL().open()) { + EJDB2Exception exception = null; + String json = "{'foo':'bar'}".replace('\'', '"'); + String patch = "[{'op':'add', 'path':'/baz', 'value':'qux'}]".replace('\'', '"'); + + long id = db.put("c1", json); + assert (id == 1L); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + db.get("c1", 1L, bos); + assert (bos.toString().equals(json)); + + db.patch("c1", patch, id); + bos.reset(); + db.get("c1", 1L, bos); + assert (bos.toString().equals("{'foo':'bar','baz':'qux'}".replace('\'', '"'))); + bos.reset(); + + db.del("c1", id); + + exception = null; + try { + db.get("c1", id, bos); + } catch (EJDB2Exception e) { + exception = e; + } + assert (exception != null); + assert (exception.getMessage().contains("IWKV_ERROR_NOTFOUND")); + + // JQL resources can be closed explicitly or garbage collected + JQL q = db.createQuery("@mycoll/*"); + assert (q.getDb() != null); + assert (Objects.equals(q.getCollection(), "mycoll")); + + id = db.put("mycoll", "{'foo':'bar'}".replace('\'', '"')); + assert (id == 1); + + exception = null; + try { + db.put("mycoll", "{\""); + } catch (EJDB2Exception e) { + exception = e; + } + assert (exception != null && exception.getMessage() != null); + assert (exception.getCode() == 86005); + assert (exception.getMessage().contains("JBL_ERROR_PARSE_UNQUOTED_STRING")); + + db.put("mycoll", "{'foo':'baz'}".replace('\'', '"')); + + Map results = new LinkedHashMap<>(); + q.executeRaw((docId, doc) -> { + assert (docId > 0 && doc != null); + results.put(docId, doc); + return 1; + }); + assert (results.size() == 2); + assert (Objects.equals(results.get(1L), "{\"foo\":\"bar\"}")); + assert (Objects.equals(results.get(2L), "{\"foo\":\"baz\"}")); + results.clear(); + + try (JQL q2 = db.createQuery("/[foo=:?]", "mycoll").setString(0, "zaz")) { + q2.executeRaw((docId, doc) -> { + results.put(docId, doc); + return 1; + }); + } + assert (results.isEmpty()); + + try (JQL q2 = db.createQuery("/[foo=:val]", "mycoll").setString("val", "bar")) { + q2.executeRaw((docId, doc) -> { + results.put(docId, doc); + return 1; + }); + } + assert (results.size() == 1); + assert (Objects.equals(results.get(1L), "{\"foo\":\"bar\"}")); + + exception = null; + try { + db.createQuery("@mycoll/["); + } catch (EJDB2Exception e) { + exception = e; + } + assert (exception != null && exception.getMessage() != null); + assert (exception.getCode() == 87001); + assert (exception.getMessage().contains("@mycoll/[ <---")); + + long count = db.createQuery("@mycoll/* | count").executeScalarInt(); + assert (count == 2); + + q.withExplain().execute(); + assert (q.getExplainLog().contains("[INDEX] NO [COLLECTOR] PLAIN")); + + json = db.infoAsString(); + assert (json != null); + assert (json.contains("{\"name\":\"mycoll\",\"dbid\":4,\"rnum\":2,\"indexes\":[]}")); + + // Indexes + db.ensureStringIndex("mycoll", "/foo", true); + json = db.infoAsString(); + assert (json.contains("\"indexes\":[{\"ptr\":\"/foo\",\"mode\":5,\"idbf\":0,\"dbid\":5,\"rnum\":2}]")); + + db.patch("mycoll", patch, 2); + + json = db.createQuery("@mycoll/[foo=:?] and /[baz=:?]").setString(0, "baz").setString(1, "qux").firstValue(); + assert ("{\"foo\":\"baz\",\"baz\":\"qux\"}".equals(json)); + + json = db.createQuery("@mycoll/[foo re :?]").setRegexp(0, ".*").firstValue(); + assert ("{\"foo\":\"baz\",\"baz\":\"qux\"}".equals(json)); + + db.removeStringIndex("mycoll", "/foo", true); + json = db.infoAsString(); + assert (json.contains("{\"name\":\"mycoll\",\"dbid\":4,\"rnum\":2,\"indexes\":[]}")); + + db.removeCollection("mycoll"); + db.removeCollection("c1"); + json = db.infoAsString(); + assert (json.contains("\"collections\":[]")); + + // Test rename collection + bos.reset(); + db.put("cc1", "{\"foo\": 1}"); + db.renameCollection("cc1", "cc2"); + db.get("cc2", 1, bos); + assert (bos.toString().equals("{\"foo\":1}")); + + // Check limit + q = db.createQuery("@mycoll/* | limit 2 skip 3"); + assert (q.getLimit() == 2); + assert (q.getSkip() == 3); + + long ts0 = System.currentTimeMillis(); + long ts = db.onlineBackup("test-bkp.db"); + assert (ts > ts0); + assert (new File("test-bkp.db").exists()); + } + + try (EJDB2 db = new EJDB2Builder("test-bkp.db").withWAL().open()) { + String val = db.getAsString("cc2", 1); + assert (val.equals("{\"foo\":1}")); + } + } + + public static void main(String[] args) throws Exception { + jsonBasicTest(); + dbTest(); + } +} diff --git a/src/bindings/ejdb2_jni/version.txt b/src/bindings/ejdb2_jni/version.txt new file mode 100644 index 0000000..3cacc0b --- /dev/null +++ b/src/bindings/ejdb2_jni/version.txt @@ -0,0 +1 @@ +12 \ No newline at end of file diff --git a/src/bindings/ejdb2_node/.eslintrc b/src/bindings/ejdb2_node/.eslintrc new file mode 100644 index 0000000..60a52e3 --- /dev/null +++ b/src/bindings/ejdb2_node/.eslintrc @@ -0,0 +1,55 @@ +{ + "env": { + "commonjs": true, + "es6": true, + "node": true + }, + "parserOptions": { + "ecmaVersion": 2018, + "ecmaFeatures": { + "jsx": false + }, + "sourceType": "module" + }, + "rules": { + "no-const-assign": "error", + "no-undef": "off", + "no-unreachable": "warn", + "no-unused-vars": "warn", + "no-redeclare": "error", + "no-octal": "error", + "no-self-assign": "error", + "no-unused-labels": "warn", + "no-useless-return": "warn", + "constructor-super": "warn", + "valid-typeof": "warn", + "no-mixed-spaces-and-tabs": "warn", + "no-delete-var": "error", + "no-class-assign": "error", + "no-this-before-super": "warn", + "require-yield": "warn", + "quotes": [ + "warn", + "single" + ], + "semi": [ + "warn", + "always" + ], + "semi-spacing": [ + "warn", + { + "before": false, + "after": true + } + ], + "no-extra-semi": "error", + "brace-style": [ + "error", + "1tbs", + { + "allowSingleLine": true + } + ] + } +} \ No newline at end of file diff --git a/src/bindings/ejdb2_node/.gitignore b/src/bindings/ejdb2_node/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/src/bindings/ejdb2_node/CHANGELOG.md b/src/bindings/ejdb2_node/CHANGELOG.md new file mode 100644 index 0000000..4639e04 --- /dev/null +++ b/src/bindings/ejdb2_node/CHANGELOG.md @@ -0,0 +1,3 @@ +ejdb_node (@EJDB2_NODE_VERSION@) + +- Fixed Stalling under stress issue (#306) diff --git a/src/bindings/ejdb2_node/CMakeLists.txt b/src/bindings/ejdb2_node/CMakeLists.txt new file mode 100644 index 0000000..9be89fd --- /dev/null +++ b/src/bindings/ejdb2_node/CMakeLists.txt @@ -0,0 +1,92 @@ +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/version.txt _VERSION) +set_property(GLOBAL PROPERTY EJDB2_NODE_VERSION_PROPERTY + "${PROJECT_VERSION}${_VERSION}") +set(EJDB2_NODE_VERSION "${PROJECT_VERSION}${_VERSION}") + +if(NOT DEFINED NODE_PUB_DIR) + set(NODE_PUB_DIR ${CMAKE_CURRENT_BINARY_DIR}/ejdb2_node) +endif() +if(NOT DEFINED NODE_BIN_ROOT) + set(NODE_BIN_ROOT ${NODE_PUB_DIR}) +endif() + +# Nodejs process.arch: 'arm', 'arm64', 'ia32', 'ppc', 'ppc64', 's390', 's390x', +# 'x32', and 'x64 +if(MSVC64 OR MINGW64) + set(PROCESSOR x64) +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(amd64.*|x86_64.*|AMD64.*)") + set(PROCESSOR x64) +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(i686.*|i386.*|x86.*)") + set(PROCESSOR x32) +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64.*|AARCH64.*)") + set(PROCESSOR arm64) +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm.*|ARM.*)") + set(PROCESSOR arm) +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64.*") + set(PROCESSOR ppc64) +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)") + set(PROCESSOR ppc) +else() + set(PROCESSOR ${CMAKE_SYSTEM_PROCESSOR}) +endif() + +string(TOLOWER ${CMAKE_SYSTEM_NAME}-${PROCESSOR} NODE_BIN_SUFFIX) +set(NODE_BIN_DIR ${NODE_BIN_ROOT}/${NODE_BIN_SUFFIX}) + +find_program(YARN_EXEC yarn) +if(YARN_EXEC MATCHES "YARN_EXEC-NOTFOUND") + message(FATAL_ERROR "`yarn` executable not found") +endif() + +add_library(ejdb2_node MODULE ejdb2_node.c) +target_link_libraries(ejdb2_node ejdb2_s ${PROJECT_LLIBRARIES}) + +set_target_properties( + ejdb2_node PROPERTIES OUTPUT_NAME ejdb2_node PREFIX "" SUFFIX ".node" + LIBRARY_OUTPUT_DIRECTORY ${NODE_BIN_DIR}) + +target_compile_options(ejdb2_node PRIVATE -DIW_API_EXPORTS + -DNODE_ADDON_API_DISABLE_DEPRECATED) + +if(APPLE) + set_target_properties( + ejdb2_node PROPERTIES LINK_FLAGS "-rdynamic -undefined dynamic_lookup") +else() + set_target_properties( + ejdb2_node PROPERTIES LINK_FLAGS + "-rdynamic -Wl,--unresolved-symbols=ignore-all") +endif() + +if(CMAKE_BUILD_TYPE STREQUAL "Release") + add_custom_command( + TARGET ejdb2_node + POST_BUILD + COMMAND strip -s $) +endif() + +configure_file(package.json ${NODE_PUB_DIR}/package.json @ONLY) +configure_file(CHANGELOG.md ${NODE_PUB_DIR}/CHANGELOG.md @ONLY) +configure_file(README.md ${NODE_PUB_DIR}/README.md COPYONLY) +configure_file(yarn.lock ${NODE_PUB_DIR}/yarn.lock COPYONLY) +configure_file(index.js ${NODE_PUB_DIR}/index.js COPYONLY) +configure_file(index.d.ts ${NODE_PUB_DIR}/index.d.ts COPYONLY) +configure_file(binary.js ${NODE_PUB_DIR}/binary.js COPYONLY) +configure_file(utils.js ${NODE_PUB_DIR}/utils.js COPYONLY) +configure_file(install.js ${NODE_PUB_DIR}/install.js COPYONLY) +configure_file(test.js ${NODE_PUB_DIR}/test.js COPYONLY) + +add_custom_command( + COMMAND yarn install + OUTPUT ${NODE_PUB_DIR}/node_modules + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/yarn.lock + ${CMAKE_CURRENT_SOURCE_DIR}/package.json + WORKING_DIRECTORY ${NODE_PUB_DIR} + VERBATIM) +add_custom_target(yarn ALL DEPENDS ${NODE_PUB_DIR}/node_modules) + +if(BUILD_TESTS) + add_test( + NAME ejdb2node + COMMAND ${YARN_EXEC} run test + WORKING_DIRECTORY ${NODE_PUB_DIR}) +endif() diff --git a/src/bindings/ejdb2_node/README.md b/src/bindings/ejdb2_node/README.md new file mode 100644 index 0000000..c7c01c5 --- /dev/null +++ b/src/bindings/ejdb2_node/README.md @@ -0,0 +1,66 @@ +# EJDB2 Node.js native binding + +Embeddable JSON Database engine http://ejdb.org Node.js binding. + +See https://github.com/Softmotions/ejdb/blob/master/README.md + +For API usage examples take a look into [/example](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_node/example) and [test.js](https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_node/test.js) + +## Note on versioning + +Since `2020-12-22` a new version scheme is used and the major version +of node package has been incremented. Now package version is the concatenation +of ejdb2 core engine version and version serial number of this binding. +For example: given ejdb2 version `2.0.52` and binding serial `2` +the actual package version will be `2.0.522`. + +## Sample code + +```ts +import { EJDB2 } from "ejdb2_node"; + +async function run() { + const db = await EJDB2.open("example.db", { truncate: true }); + + var id = await db.put("parrots", { name: "Bianca", age: 4 }); + console.log(`Bianca record: ${id}`); + + id = await db.put("parrots", { name: "Darko", age: 8 }); + console.log(`Darko record: ${id}`); + + const q = db.createQuery("/[age > :age]", "parrots"); + + for await (const doc of q.setNumber("age", 3).stream()) { + console.log(`Found ${doc}`); + } + + await db.close(); +} + +run(); +``` + +## Supported platforms + +- Linux x64 +- OSX + +## Prerequisites + +- node >= v10.0.0 +- yarn +- CMake >= v3.10 +- Make +- gcc or clang compiler + +## How build it manually + +```sh +git clone https://github.com/Softmotions/ejdb.git +cd ./ejdb +mkdir ./build && cd build +cmake .. -DBUILD_NODEJS_BINDING=ON -DCMAKE_BUILD_TYPE=Release +make +cd src/bindings/ejdb2_node/ejdb2_node +yarn pack +``` diff --git a/src/bindings/ejdb2_node/binary.js b/src/bindings/ejdb2_node/binary.js new file mode 100644 index 0000000..6200016 --- /dev/null +++ b/src/bindings/ejdb2_node/binary.js @@ -0,0 +1,36 @@ +/************************************************************************************************** + * EJDB2 Node.js native API binding. + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +const path = require('path'); +const utils = require('./utils'); + +/* + * Load native library with specified name. + */ +module.exports = function (name) { + const file = path.join(__dirname, utils.binariesDir, name); + return require(file); +}; \ No newline at end of file diff --git a/src/bindings/ejdb2_node/ejdb2_node.c b/src/bindings/ejdb2_node/ejdb2_node.c new file mode 100644 index 0000000..99cc457 --- /dev/null +++ b/src/bindings/ejdb2_node/ejdb2_node.c @@ -0,0 +1,2091 @@ +#include +#define NAPI_EXPERIMENTAL +#include "node_api.h" +#include +#include +#include +#include +#include +#include + +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + +static void jn_resultset_tsf( + napi_env env, + napi_value js_add_stream, + void *context, + void *data); +static bool jn_throw_error(napi_env env, iwrc rc, const char *location, const char *msg); +static napi_value jn_create_error(napi_env env, iwrc rc, const char *location, const char *msg); + +IW_INLINE bool jn_is_exception_pending(napi_env env) { + bool rv = false; + napi_is_exception_pending(env, &rv); + return rv; +} + +#define JNTHROW(env, rc, message) \ + jn_throw_error(env, rc, "ejdb2_node.c" ":" STR(__LINE__), message) + +#define JNTHROW_LAST(env) do { \ + const napi_extended_error_info *info = 0; \ + napi_get_last_error_info((env), &info); \ + if (info) JNTHROW((env), 0, info->error_message); \ +} while (0) + +#define JNCHECK(ns, env) do { \ + if (ns && ns != napi_pending_exception) { \ + JNTHROW_LAST(env); \ + } \ +} while (0) + +#define JNRC(env, rc) do { \ + if (rc && !jn_is_exception_pending(env)) { \ + JNTHROW(env, rc, 0); \ + } \ +} while (0) + +#define JNRET(ns, env, call, res) do { \ + ns = (call); \ + if (ns) { \ + if (ns != napi_pending_exception) { \ + JNTHROW_LAST(env); \ + } \ + return (res); \ + } \ +} while (0) + +#define JNGO(ns, env, call, label) do { \ + ns = (call); \ + if (ns) { \ + if (ns != napi_pending_exception) { \ + JNTHROW_LAST(env); \ + } \ + goto label; \ + } \ +} while (0) + +static napi_value jn_create_error(napi_env env, iwrc rc, const char *location, const char *msg) { + // Eg: + // Error [@ejdb IWRC:70002 open]: IO error with expected errno status set. (IW_ERROR_IO_ERRNO) + char codebuf[255]; + const char *code = codebuf; + if (!msg) { + if (rc) { + msg = iwlog_ecode_explained(rc); + } else { + msg = ""; + } + } + if (rc) { + iwrc_strip_code(&rc); + if (location) { + snprintf(codebuf, sizeof(codebuf), "@ejdb IWRC:%" PRId64 " %s", rc, location); + } else { + snprintf(codebuf, sizeof(codebuf), "@ejdb IWRC:%" PRId64, rc); + } + } else { + code = "@ejdb"; + } + napi_status ns; + napi_value vcode, vmsg, verr = 0; + JNGO(ns, env, napi_create_string_utf8(env, code, NAPI_AUTO_LENGTH, &vcode), finish); + JNGO(ns, env, napi_create_string_utf8(env, msg, NAPI_AUTO_LENGTH, &vmsg), finish); + JNGO(ns, env, napi_create_error(env, vcode, vmsg, &verr), finish); + +finish: + return verr; +} + +IW_INLINE bool jn_throw_error(napi_env env, iwrc rc, const char *location, const char *msg) { + napi_value verr = jn_create_error(env, rc, location, msg); + return verr && napi_throw(env, verr) == napi_ok; +} + +typedef enum { + _JN_ERROR_START = (IW_ERROR_START + 15000UL + 6000), + JN_ERROR_INVALID_NATIVE_CALL_ARGS, /**< Invalid native function call args (JN_ERROR_INVALID_NATIVE_CALL_ARGS) */ + JN_ERROR_INVALID_STATE, /**< Invalid native extension state (JN_ERROR_INVALID_STATE) */ + JN_ERROR_QUERY_IN_USE, + /**< Query object is in use by active async iteration, and cannot be changed + (JN_ERROR_QUERY_IN_USE) */ + JN_ERROR_NAPI, /*< N-API Error (JN_ERROR_NAPI) */ + _JN_ERROR_END, +} jn_ecode_t; + +typedef struct JBN { + EJDB db; + IWPOOL *pool; + EJDB_OPTS opts; + napi_threadsafe_function resultset_tsf; +} *JBN; + +typedef struct JNWORK { + iwrc rc; // RC error + napi_status ns; + napi_deferred deferred; + napi_async_work async_work; + const char *async_resource; + void *unwrapped; + void *data; + void (*release_data)(napi_env env, struct JNWORK *w); + IWPOOL *pool; +} *JNWORK; + +// Globals + +static napi_ref k_vadd_streamfn_ref; + +IW_INLINE napi_value jn_get_ref(napi_env env, napi_ref ref) { + napi_value ret; + napi_status ns; + JNRET(ns, env, napi_get_reference_value(env, ref, &ret), 0); + return ret; +} + +IW_INLINE napi_value jn_null(napi_env env) { + napi_value ret; + napi_status ns; + JNRET(ns, env, napi_get_null(env, &ret), 0); + return ret; +} + +IW_INLINE napi_value jn_global(napi_env env) { + napi_value ret; + napi_status ns; + JNRET(ns, env, napi_get_global(env, &ret), 0); + return ret; +} + +IW_INLINE napi_value jn_undefined(napi_env env) { + napi_value ret; + napi_status ns; + JNRET(ns, env, napi_get_undefined(env, &ret), 0); + return ret; +} + +IW_INLINE bool jn_is_null(napi_env env, napi_value val) { + bool bv = false; + napi_value rv = jn_null(env); + if (!rv) { + return false; + } + napi_strict_equals(env, val, rv, &bv); + return bv; +} + +IW_INLINE bool jn_is_undefined(napi_env env, napi_value val) { + bool bv = false; + napi_value rv = jn_undefined(env); + if (!rv) { + return false; + } + napi_strict_equals(env, val, rv, &bv); + return bv; +} + +IW_INLINE bool jn_is_null_or_undefined(napi_env env, napi_value val) { + return jn_is_null(env, val) || jn_is_undefined(env, val); +} + +IW_INLINE napi_value jn_create_string(napi_env env, const char *str) { + napi_value ret; + napi_status ns; + JNRET(ns, env, napi_create_string_utf8(env, str, NAPI_AUTO_LENGTH, &ret), 0); + return ret; +} + +IW_INLINE napi_value jn_create_int64(napi_env env, int64_t val) { + napi_value ret; + napi_status ns; + JNRET(ns, env, napi_create_int64(env, val, &ret), 0); + return ret; +} + +static char *jn_string(napi_env env, napi_value val_, IWPOOL *pool, bool nulls, bool coerce, iwrc *rcp) { + *rcp = 0; + size_t len = 0; + napi_status ns = 0; + napi_value val = val_; + if (nulls && jn_is_null_or_undefined(env, val)) { + return 0; + } + if (coerce) { + ns = napi_coerce_to_string(env, val, &val); + if (ns) { + *rcp = JN_ERROR_NAPI; + JNCHECK(ns, env); + return 0; + } + } + ns = napi_get_value_string_utf8(env, val, 0, 0, &len); + if (ns) { + *rcp = JN_ERROR_NAPI; + JNCHECK(ns, env); + return 0; + } + if (!len) { + return 0; + } + char *buf = iwpool_alloc(len + 1, pool); + if (!buf) { + *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return 0; + } + ns = napi_get_value_string_utf8(env, val, buf, len + 1, &len); + if (ns) { + *rcp = JN_ERROR_NAPI; + JNCHECK(ns, env); + return 0; + } + return buf; +} + +static char *jn_string_at( + napi_env env, IWPOOL *pool, napi_value arr, + bool nulls, bool coerce, uint32_t idx, iwrc *rcp) { + *rcp = 0; + napi_value el; + napi_status ns = napi_get_element(env, arr, idx, &el); + if (ns) { + *rcp = JN_ERROR_NAPI; + JNCHECK(ns, env); + return 0; + } + return jn_string(env, el, pool, nulls, coerce, rcp); +} + +static int64_t jn_int(napi_env env, napi_value val_, bool nulls, bool coerce, iwrc *rcp) { + *rcp = 0; + napi_status ns = 0; + napi_value val = val_; + if (nulls && jn_is_null_or_undefined(env, val)) { + return 0; + } + if (coerce) { + ns = napi_coerce_to_number(env, val, &val); + if (ns) { + *rcp = JN_ERROR_NAPI; + JNCHECK(ns, env); + return 0; + } + } + int64_t rv = 0; + ns = napi_get_value_int64(env, val, &rv); + if (ns) { + *rcp = JN_ERROR_NAPI; + JNCHECK(ns, env); + return 0; + } + return rv; +} + +static int64_t jn_int_at(napi_env env, napi_value arr, bool nulls, bool coerce, uint32_t idx, iwrc *rcp) { + *rcp = 0; + napi_value el; + napi_status ns = napi_get_element(env, arr, idx, &el); + if (ns) { + *rcp = JN_ERROR_NAPI; + JNCHECK(ns, env); + return 0; + } + return jn_int(env, el, nulls, coerce, rcp); +} + +static double jn_double(napi_env env, napi_value val_, bool nulls, bool coerce, iwrc *rcp) { + *rcp = 0; + napi_status ns = 0; + napi_value val = val_; + if (nulls && jn_is_null_or_undefined(env, val)) { + return 0; + } + if (coerce) { + ns = napi_coerce_to_number(env, val, &val); + if (ns) { + *rcp = JN_ERROR_NAPI; + JNCHECK(ns, env); + return 0; + } + } + double rv = 0; + ns = napi_get_value_double(env, val, &rv); + if (ns) { + *rcp = JN_ERROR_NAPI; + JNCHECK(ns, env); + return 0; + } + return rv; +} + +static bool jn_bool(napi_env env, napi_value val_, bool nulls, bool coerce, iwrc *rcp) { + *rcp = 0; + napi_status ns = 0; + napi_value val = val_; + if (nulls && jn_is_null_or_undefined(env, val)) { + return false; + } + if (coerce) { + ns = napi_coerce_to_bool(env, val, &val); + if (ns) { + *rcp = JN_ERROR_NAPI; + JNCHECK(ns, env); + return false; + } + } + bool rv = false; + ns = napi_get_value_bool(env, val, &rv); + if (ns) { + *rcp = JN_ERROR_NAPI; + JNCHECK(ns, env); + return false; + } + return rv; +} + +static bool jn_bool_at(napi_env env, napi_value arr, bool nulls, bool coerce, uint32_t idx, iwrc *rcp) { + *rcp = 0; + napi_value el; + napi_status ns = napi_get_element(env, arr, idx, &el); + if (ns) { + *rcp = JN_ERROR_NAPI; + JNCHECK(ns, env); + return false; + } + return jn_bool(env, el, nulls, coerce, rcp); +} + +static iwrc jb_jbn_alloc(JBN *vp) { + *vp = 0; + IWPOOL *pool = iwpool_create(255); + if (!pool) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + JBN v = iwpool_calloc(sizeof(*v), pool); + if (!v) { + iwrc rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + iwpool_destroy(pool); + return rc; + } + v->pool = pool; + *vp = v; + return 0; +} + +static void jn_jbn_destroy(napi_env env, JBN *vp) { + if (!vp || !*vp) { + return; + } + JBN v = *vp; + if (v->db) { + iwrc rc = ejdb_close(&v->db); + if (rc) { + iwlog_ecode_error3(rc); + } + JNRC(env, rc); + } + if (v->resultset_tsf) { + napi_release_threadsafe_function(v->resultset_tsf, napi_tsfn_abort); + } + if (v->pool) { + iwpool_destroy(v->pool); + } + *vp = 0; +} + +static JNWORK jn_work_create(iwrc *rcp) { + *rcp = 0; + IWPOOL *pool = iwpool_create(255); + if (!pool) { + *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return 0; + } + JNWORK w = iwpool_calloc(sizeof(*w), pool); + if (!w) { + *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno); + iwpool_destroy(pool); + return 0; + } + w->pool = pool; + return w; +} + +static void *jn_work_alloc_data(size_t siz, JNWORK work, iwrc *rcp) { + *rcp = 0; + work->data = iwpool_calloc(siz, work->pool); + if (!work->data) { + *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + return work->data; +} + +void jn_work_destroy(napi_env env, JNWORK *wp) { + if (!wp || !*wp) { + return; + } + JNWORK w = *wp; + if (w->deferred) { + // Reject promise with undefined value + napi_reject_deferred(env, w->deferred, jn_undefined(env)); + } + if (w->async_work) { + napi_delete_async_work(env, w->async_work); + } + if (w->release_data) { + w->release_data(env, w); + } + if (w->pool) { + iwpool_destroy(w->pool); + } + *wp = 0; +} + +static void jn_ejdb2impl_finalize(napi_env env, void *data, void *hint) { + JBN jbn = data; + jn_jbn_destroy(env, &jbn); +} + +// string array opts +static napi_value jn_ejdb2impl_ctor(napi_env env, napi_callback_info info) { + iwrc rc = 0; + napi_status ns = 0; + + size_t argc = 1; + bool bv = false; + uint32_t ulv = 0; + + JBN jbn = 0; + napi_value varr, this; + void *data; + + JNGO(ns, env, napi_get_cb_info(env, info, &argc, &varr, &this, &data), finish); + if (argc != 1) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + JNRC(env, rc); + goto finish; + } + + napi_is_array(env, varr, &bv); + if (!bv) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + JNRC(env, rc); + goto finish; + } + JNGO(ns, env, napi_get_array_length(env, varr, &ulv), finish); + if (ulv != 16) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + JNRC(env, rc); + goto finish; + } + + rc = jb_jbn_alloc(&jbn); + RCGO(rc, finish); + + // opts.kv.path // non null + // opts.kv.oflags // non null + // opts.kv.wal.enabled // non null + // opts.kv.wal.check_crc_on_checkpoint + // opts.kv.wal.checkpoint_buffer_sz + // opts.kv.wal.checkpoint_timeout_sec + // opts.kv.wal.savepoint_timeout_sec + // opts.kv.wal.wal_buffer_sz + // opts.document_buffer_sz + // opts.sort_buffer_sz + // opts.http.enabled + // opts.http.access_token + // opts.http.bind + // opts.http.max_body_size + // opts.http.port + // opts.http.read_anon + + argc = 0; + jbn->opts.kv.path = jn_string_at(env, jbn->pool, varr, false, false, argc++, &rc); + RCGO(rc, finish); + jbn->opts.kv.oflags = jn_int_at(env, varr, false, false, argc++, &rc); + RCGO(rc, finish); + jbn->opts.kv.wal.enabled = jn_bool_at(env, varr, false, false, argc++, &rc); + RCGO(rc, finish); + jbn->opts.kv.wal.check_crc_on_checkpoint = jn_bool_at(env, varr, true, true, argc++, &rc); + RCGO(rc, finish); + jbn->opts.kv.wal.checkpoint_buffer_sz = jn_int_at(env, varr, true, false, argc++, &rc); + RCGO(rc, finish); + jbn->opts.kv.wal.checkpoint_timeout_sec = jn_int_at(env, varr, true, false, argc++, &rc); + RCGO(rc, finish); + jbn->opts.kv.wal.savepoint_timeout_sec = jn_int_at(env, varr, true, false, argc++, &rc); + RCGO(rc, finish); + jbn->opts.kv.wal.wal_buffer_sz = jn_int_at(env, varr, true, false, argc++, &rc); + RCGO(rc, finish); + jbn->opts.document_buffer_sz = jn_int_at(env, varr, true, false, argc++, &rc); + RCGO(rc, finish); + jbn->opts.sort_buffer_sz = jn_int_at(env, varr, true, false, argc++, &rc); + RCGO(rc, finish); + jbn->opts.http.enabled = jn_bool_at(env, varr, true, true, argc++, &rc); + RCGO(rc, finish); + jbn->opts.http.access_token = jn_string_at(env, jbn->pool, varr, true, false, argc++, &rc); + RCGO(rc, finish); + jbn->opts.http.bind = jn_string_at(env, jbn->pool, varr, true, false, argc++, &rc); + RCGO(rc, finish); + jbn->opts.http.max_body_size = jn_int_at(env, varr, true, false, argc++, &rc); + RCGO(rc, finish); + jbn->opts.http.port = jn_int_at(env, varr, true, false, argc++, &rc); + RCGO(rc, finish); + jbn->opts.http.read_anon = jn_bool_at(env, varr, true, true, argc++, &rc); + RCGO(rc, finish); + + jbn->opts.kv.file_lock_fail_fast = true; + jbn->opts.no_wal = !jbn->opts.kv.wal.enabled; + jbn->opts.http.blocking = false; + jbn->opts.http.access_token_len = jbn->opts.http.access_token ? strlen(jbn->opts.http.access_token) : 0; + + // Result-set thread-save function initialization + napi_value vadd_streamfn; + JNGO(ns, env, napi_get_reference_value(env, k_vadd_streamfn_ref, &vadd_streamfn), finish); + JNGO(ns, env, napi_create_threadsafe_function( + env, // napi_env env, + vadd_streamfn, // napi_value func, + 0, // napi_value async_resource, + jn_create_string(env, "jn_add_stream_result"), // napi_value async_resource_name, + 1, // size_t max_queue_size, + 1, // size_t initial_thread_count, + 0, // void* thread_finalize_data, + 0, // napi_finalize thread_finalize_cb, + 0, // void* context, + jn_resultset_tsf, // napi_threadsafe_function_call_js call_js_cb, + &jbn->resultset_tsf // napi_threadsafe_function* result + ), finish); + + // Wrap class instance + JNGO(ns, env, napi_wrap(env, this, jbn, jn_ejdb2impl_finalize, 0, 0), finish); + +finish: + if (jn_is_exception_pending(env) || rc) { + JNRC(env, rc); + if (jbn) { + jn_jbn_destroy(env, &jbn); + } + return 0; + } + return this; +} + +#define JNFUNC(func) {#func, 0, jn_ ## func, 0, 0, 0, napi_default, 0 } +#define JNVAL(name, value) {#name, 0, 0, 0, 0, value, napi_default, 0 } + +bool jn_resolve_pending_errors(napi_env env, napi_status ns, JNWORK work) { + assert(work); + if (!work->deferred) { + return true; + } + bool pending = jn_is_exception_pending(env); + if (!(work->rc || ns || pending)) { + return false; + } + napi_value ex; + if (pending && (!work->rc || (work->rc == JN_ERROR_NAPI))) { + ns = napi_get_and_clear_last_exception(env, &ex); + if (ns == napi_ok) { + napi_reject_deferred(env, work->deferred, ex); + } + } else { + napi_value verr; + const napi_extended_error_info *info = 0; + if (ns) { + napi_get_last_error_info(env, &info); + } + if (pending) { + napi_get_and_clear_last_exception(env, &ex); + } + verr = jn_create_error(env, work->rc, work->async_resource, info ? info->error_message : 0); + if (verr) { + napi_reject_deferred(env, work->deferred, verr); + } + } + work->deferred = 0; + return true; +} + +static napi_value jn_launch_promise( + napi_env env, + napi_callback_info info, + const char *async_resource_name, + napi_async_execute_callback execute, + napi_async_complete_callback complete, + JNWORK work) { + size_t argc = 0; + napi_status ns = 0; + napi_value promise = 0, this, awork; + void *data; + + work->async_resource = async_resource_name; + JNGO(ns, env, napi_get_cb_info(env, info, &argc, 0, &this, &data), finish); + JNGO(ns, env, napi_unwrap(env, this, &work->unwrapped), finish); + JNGO(ns, env, napi_create_promise(env, &work->deferred, &promise), finish); + JNGO(ns, env, napi_create_string_utf8(env, async_resource_name, NAPI_AUTO_LENGTH, &awork), finish); + JNGO(ns, env, napi_create_async_work(env, 0, awork, execute, complete, work, &work->async_work), finish); + JNGO(ns, env, napi_queue_async_work(env, work->async_work), finish); + +finish: + if (ns || jn_is_exception_pending(env)) { + if (work) { + jn_resolve_pending_errors(env, ns, work); + jn_work_destroy(env, &work); + } + } + return promise; +} + +// ---------------- EJDB2.open() + +static void jn_open_execute(napi_env env, void *data) { + JNWORK work = data; + JBN jbn = work->unwrapped; + if (jbn->db) { + return; // Database is already opened + } + work->rc = ejdb_open(&jbn->opts, &jbn->db); +} + +static void jn_open_complete(napi_env env, napi_status ns, void *data) { + JNWORK work = data; + if (jn_resolve_pending_errors(env, ns, work)) { + goto finish; + } + JNGO(ns, env, napi_resolve_deferred(env, work->deferred, jn_undefined(env)), finish); + work->deferred = 0; + +finish: + jn_work_destroy(env, &work); +} + +static napi_value jn_open(napi_env env, napi_callback_info info) { + iwrc rc; + JNWORK work = jn_work_create(&rc); + if (rc) { + JNRC(env, rc); + return jn_undefined(env); + } + napi_value ret = jn_launch_promise(env, info, "open", jn_open_execute, jn_open_complete, work); + return ret ? ret : jn_undefined(env); +} + +// ---------------- EJDB2.close() + +static void jn_close_execute(napi_env env, void *data) { + JNWORK work = data; + JBN jbn = work->unwrapped; + if (!jbn->db) { + return; + } + work->rc = ejdb_close(&jbn->db); +} + +static void jn_close_complete(napi_env env, napi_status ns, void *data) { + JNWORK work = data; + JBN jbn = work->unwrapped; + if (jn_resolve_pending_errors(env, ns, work)) { + goto finish; + } + JNGO(ns, env, napi_resolve_deferred(env, work->deferred, jn_undefined(env)), finish); + work->deferred = 0; + +finish: + if (jbn->resultset_tsf) { + napi_release_threadsafe_function(jbn->resultset_tsf, napi_tsfn_abort); + jbn->resultset_tsf = 0; + } + jn_work_destroy(env, &work); +} + +static napi_value jn_close(napi_env env, napi_callback_info info) { + iwrc rc; + JNWORK work = jn_work_create(&rc); + if (rc) { + JNRC(env, rc); + return jn_undefined(env); + } + napi_value ret = jn_launch_promise(env, info, "close", jn_close_execute, jn_close_complete, work); + return ret ? ret : jn_undefined(env); +} + +// ---------------- EJDB2.put/patch() + +struct JNPUT_DATA { + int64_t id; + const char *coll; + const char *json; + bool patch; +}; + +static void jn_put_execute(napi_env env, void *data) { + JBL jbl = 0; + JNWORK work = data; + JBN jbn = work->unwrapped; + if (!jbn->db) { + work->rc = JN_ERROR_INVALID_STATE; + goto finish; + } + struct JNPUT_DATA *wdata = work->data; + if (!wdata->patch) { + work->rc = jbl_from_json(&jbl, wdata->json); + RCGO(work->rc, finish); + } + if (wdata->id > 0) { + if (wdata->patch) { + work->rc = ejdb_patch(jbn->db, wdata->coll, wdata->json, wdata->id); + } else { + work->rc = ejdb_put(jbn->db, wdata->coll, jbl, wdata->id); + } + } else { + work->rc = ejdb_put_new(jbn->db, wdata->coll, jbl, &wdata->id); + } +finish: + if (jbl) { + jbl_destroy(&jbl); + } +} + +static void jn_put_complete(napi_env env, napi_status ns, void *data) { + napi_value rv; + JNWORK work = data; + if (jn_resolve_pending_errors(env, ns, work)) { + goto finish; + } + struct JNPUT_DATA *wdata = work->data; + ns = napi_create_int64(env, wdata->id, &rv); + if (ns) { + jn_resolve_pending_errors(env, ns, work); + goto finish; + } + JNGO(ns, env, napi_resolve_deferred(env, work->deferred, rv), finish); + work->deferred = 0; + +finish: + jn_work_destroy(env, &work); +} + +// collection, json, id +static napi_value jn_put_patch(napi_env env, napi_callback_info info, bool patch, bool upsert) { + iwrc rc = 0; + napi_status ns = 0; + napi_value this, argv[3] = { 0 }; + size_t argc = sizeof(argv) / sizeof(argv[0]); + void *data; + napi_value ret = 0; + JNWORK work = jn_work_create(&rc); + RCGO(rc, finish); + + JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish); + if (argc != sizeof(argv) / sizeof(argv[0])) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + struct JNPUT_DATA *wdata = jn_work_alloc_data(sizeof(*wdata), work, &rc); + RCGO(rc, finish); + wdata->patch = patch; + wdata->coll = jn_string(env, argv[0], work->pool, false, true, &rc); + RCGO(rc, finish); + wdata->json = jn_string(env, argv[1], work->pool, false, false, &rc); + RCGO(rc, finish); + wdata->id = jn_int(env, argv[2], true, true, &rc); + RCGO(rc, finish); + + if ((wdata->id < 1) && wdata->patch) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + ret = jn_launch_promise(env, info, wdata->patch ? "patch" : "put", jn_put_execute, jn_put_complete, work); + +finish: + if (rc) { + JNRC(env, rc); + if (work) { + jn_work_destroy(env, &work); + } + } + return ret ? ret : jn_undefined(env); +} + +static napi_value jn_put(napi_env env, napi_callback_info info) { + return jn_put_patch(env, info, false, false); +} + +static napi_value jn_patch(napi_env env, napi_callback_info info) { + return jn_put_patch(env, info, true, false); +} + +static napi_value jn_patch_or_put(napi_env env, napi_callback_info info) { + return jn_put_patch(env, info, true, true); +} + +// ---------------- EJDB2.get() + +struct JNGET_DATA { + int64_t id; + const char *coll; + JBL jbl; +}; + +static void jn_get_data_destroy(napi_env env, JNWORK w) { + struct JNGET_DATA *wdata = w->data; + if (wdata && wdata->jbl) { + jbl_destroy(&wdata->jbl); + } +} + +static void jn_get_execute(napi_env env, void *data) { + JNWORK work = data; + JBN jbn = work->unwrapped; + if (!jbn->db) { + work->rc = JN_ERROR_INVALID_STATE; + return; + } + struct JNGET_DATA *wdata = work->data; + work->rc = ejdb_get(jbn->db, wdata->coll, wdata->id, &wdata->jbl); +} + +static void jn_get_complete(napi_env env, napi_status ns, void *data) { + napi_value rv; + JNWORK work = data; + if (jn_resolve_pending_errors(env, ns, work)) { + goto finish; + } + struct JNGET_DATA *wdata = work->data; + IWXSTR *xstr = iwxstr_new2(jbl_size(wdata->jbl) * 2); + if (!xstr) { + work->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish0; + } + work->rc = jbl_as_json(wdata->jbl, jbl_xstr_json_printer, xstr, 0); + RCGO(work->rc, finish0); + JNGO(ns, env, napi_create_string_utf8(env, iwxstr_ptr(xstr), iwxstr_size(xstr), &rv), finish0); + JNGO(ns, env, napi_resolve_deferred(env, work->deferred, rv), finish0); + work->deferred = 0; + +finish0: + if (xstr) { + iwxstr_destroy(xstr); + } + if (work->rc || ns) { + jn_resolve_pending_errors(env, ns, work); + } +finish: + jn_work_destroy(env, &work); +} + +static napi_value jn_get(napi_env env, napi_callback_info info) { + iwrc rc = 0; + napi_status ns; + napi_value this, argv[2]; + napi_value ret = 0; + size_t argc = sizeof(argv) / sizeof(argv[0]); + void *data; + + JNWORK work = jn_work_create(&rc); + RCGO(rc, finish); + + JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish); + if (argc != sizeof(argv) / sizeof(argv[0])) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + struct JNGET_DATA *wdata = jn_work_alloc_data(sizeof(*wdata), work, &rc); + RCGO(rc, finish); + work->release_data = jn_get_data_destroy; + wdata->coll = jn_string(env, argv[0], work->pool, false, false, &rc); + RCGO(rc, finish); + wdata->id = jn_int(env, argv[1], false, false, &rc); + RCGO(rc, finish); + + ret = jn_launch_promise(env, info, "get", jn_get_execute, jn_get_complete, work); + +finish: + if (rc) { + JNRC(env, rc); + if (work) { + jn_work_destroy(env, &work); + } + } + return ret ? ret : jn_undefined(env); +} + +// ---------------- EJDB2.del() + +static void jn_del_execute(napi_env env, void *data) { + JNWORK work = data; + JBN jbn = work->unwrapped; + if (!jbn->db) { + work->rc = JN_ERROR_INVALID_STATE; + return; + } + struct JNGET_DATA *wdata = work->data; + work->rc = ejdb_del(jbn->db, wdata->coll, wdata->id); +} + +static void jn_del_complete(napi_env env, napi_status ns, void *data) { + JNWORK work = data; + if (jn_resolve_pending_errors(env, ns, work)) { + goto finish; + } + JNGO(ns, env, napi_resolve_deferred(env, work->deferred, jn_undefined(env)), finish); + work->deferred = 0; + +finish: + jn_work_destroy(env, &work); +} + +static napi_value jn_del(napi_env env, napi_callback_info info) { + iwrc rc = 0; + napi_status ns; + napi_value this, argv[2]; + napi_value ret = 0; + size_t argc = sizeof(argv) / sizeof(argv[0]); + void *data; + + JNWORK work = jn_work_create(&rc); + RCGO(rc, finish); + + JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish); + if (argc != sizeof(argv) / sizeof(argv[0])) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + struct JNGET_DATA *wdata = jn_work_alloc_data(sizeof(*wdata), work, &rc); + RCGO(rc, finish); + wdata->coll = jn_string(env, argv[0], work->pool, false, false, &rc); + RCGO(rc, finish); + wdata->id = jn_int(env, argv[1], false, false, &rc); + RCGO(rc, finish); + + ret = jn_launch_promise(env, info, "del", jn_del_execute, jn_del_complete, work); + +finish: + if (rc) { + JNRC(env, rc); + if (work) { + jn_work_destroy(env, &work); + } + } + return ret ? ret : jn_undefined(env); +} + +// ---------------- EJDB2.renameCollection() + +struct JNRENAME_DATA { + const char *old_name; + const char *new_name; +}; + +static void jn_rename_collection_execute(napi_env env, void *data) { + JNWORK work = data; + JBN jbn = work->unwrapped; + if (!jbn->db) { + work->rc = JN_ERROR_INVALID_STATE; + return; + } + struct JNRENAME_DATA *wdata = work->data; + work->rc = ejdb_rename_collection(jbn->db, wdata->old_name, wdata->new_name); +} + +static void jn_rename_collection_complete(napi_env env, napi_status ns, void *data) { + JNWORK work = data; + if (jn_resolve_pending_errors(env, ns, work)) { + goto finish; + } + JNGO(ns, env, napi_resolve_deferred(env, work->deferred, jn_undefined(env)), finish); + work->deferred = 0; + +finish: + jn_work_destroy(env, &work); +} + +static napi_value jn_rename_collection(napi_env env, napi_callback_info info) { + iwrc rc = 0; + napi_status ns; + napi_value this, argv[2]; + napi_value ret = 0; + size_t argc = sizeof(argv) / sizeof(argv[0]); + void *data; + + JNWORK work = jn_work_create(&rc); + RCGO(rc, finish); + + JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish); + if (argc != sizeof(argv) / sizeof(argv[0])) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + struct JNRENAME_DATA *wdata = jn_work_alloc_data(sizeof(*wdata), work, &rc); + RCGO(rc, finish); + + wdata->old_name = jn_string(env, argv[0], work->pool, false, false, &rc); + RCGO(rc, finish); + + wdata->new_name = jn_string(env, argv[1], work->pool, false, false, &rc); + RCGO(rc, finish); + + ret = jn_launch_promise(env, info, "rename", + jn_rename_collection_execute, jn_rename_collection_complete, + work); +finish: + if (rc) { + JNRC(env, rc); + if (work) { + jn_work_destroy(env, &work); + } + } + return ret ? ret : jn_undefined(env); +} + +// ---------------- EJDB2.info() + +static void jn_info_execute(napi_env env, void *data) { + JNWORK work = data; + JBN jbn = work->unwrapped; + if (!jbn->db) { + work->rc = JN_ERROR_INVALID_STATE; + return; + } + struct JNGET_DATA *wdata = work->data; + work->rc = ejdb_get_meta(jbn->db, &wdata->jbl); +} + +static void jn_info_complete(napi_env env, napi_status ns, void *data) { + napi_value rv; + JNWORK work = data; + if (jn_resolve_pending_errors(env, ns, work)) { + goto finish; + } + struct JNGET_DATA *wdata = work->data; + IWXSTR *xstr = iwxstr_new2(jbl_size(wdata->jbl) * 2); + if (!xstr) { + work->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish0; + } + work->rc = jbl_as_json(wdata->jbl, jbl_xstr_json_printer, xstr, 0); + RCGO(work->rc, finish0); + JNGO(ns, env, napi_create_string_utf8(env, iwxstr_ptr(xstr), iwxstr_size(xstr), &rv), finish0); + + JNGO(ns, env, napi_resolve_deferred(env, work->deferred, rv), finish0); + work->deferred = 0; + +finish0: + if (xstr) { + iwxstr_destroy(xstr); + } + if (work->rc || ns) { + jn_resolve_pending_errors(env, ns, work); + } +finish: + jn_work_destroy(env, &work); +} + +static napi_value jn_info(napi_env env, napi_callback_info info) { + iwrc rc = 0; + napi_status ns; + napi_value this; + napi_value ret = 0; + size_t argc = 0; + void *data; + JNWORK work = jn_work_create(&rc); + RCGO(rc, finish); + + JNGO(ns, env, napi_get_cb_info(env, info, &argc, 0, &this, &data), finish); + jn_work_alloc_data(sizeof(struct JNGET_DATA), work, &rc); + RCGO(rc, finish); + work->release_data = jn_get_data_destroy; + ret = jn_launch_promise(env, info, "info", jn_info_execute, jn_info_complete, work); + +finish: + if (rc) { + JNRC(env, rc); + if (work) { + jn_work_destroy(env, &work); + } + } + return ret ? ret : jn_undefined(env); +} + +// ---------------- EJDB2.ensureIndex/removeIndex() + +struct JNIDX_DATA { + const char *coll; + const char *path; + ejdb_idx_mode_t mode; + bool remove; +}; + +static void jn_index_execute(napi_env env, void *data) { + JNWORK work = data; + JBN jbn = work->unwrapped; + if (!jbn->db) { + work->rc = JN_ERROR_INVALID_STATE; + return; + } + struct JNIDX_DATA *wdata = work->data; + if (wdata->remove) { + work->rc = ejdb_remove_index(jbn->db, wdata->coll, wdata->path, wdata->mode); + } else { + work->rc = ejdb_ensure_index(jbn->db, wdata->coll, wdata->path, wdata->mode); + } +} + +static void jn_index_complete(napi_env env, napi_status ns, void *data) { + JNWORK work = data; + if (jn_resolve_pending_errors(env, ns, work)) { + goto finish; + } + JNGO(ns, env, napi_resolve_deferred(env, work->deferred, jn_undefined(env)), finish); + work->deferred = 0; + +finish: + jn_work_destroy(env, &work); +} + +static napi_value jn_index(napi_env env, napi_callback_info info) { + iwrc rc = 0; + napi_value ret = 0; + napi_status ns; + napi_value this, argv[4]; + size_t argc = sizeof(argv) / sizeof(argv[0]); + void *data; + + JNWORK work = jn_work_create(&rc); + RCGO(rc, finish); + + JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish); + if (argc != sizeof(argv) / sizeof(argv[0])) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + struct JNIDX_DATA *wdata = jn_work_alloc_data(sizeof(*wdata), work, &rc); + RCGO(rc, finish); + wdata->coll = jn_string(env, argv[0], work->pool, false, false, &rc); + RCGO(rc, finish); + wdata->path = jn_string(env, argv[1], work->pool, false, false, &rc); + RCGO(rc, finish); + wdata->mode = jn_int(env, argv[2], false, false, &rc); + RCGO(rc, finish); + wdata->remove = jn_bool(env, argv[3], false, false, &rc); + RCGO(rc, finish); + + ret = jn_launch_promise(env, info, "index", jn_index_execute, jn_index_complete, work); + +finish: + if (rc) { + JNRC(env, rc); + if (work) { + jn_work_destroy(env, &work); + } + } + return ret ? ret : jn_undefined(env); +} + +// ---------------- EJDB2.removeCollection() + +struct JNRMC_DATA { + const char *coll; +}; + +static void jn_rmcoll_execute(napi_env env, void *data) { + JNWORK work = data; + JBN jbn = work->unwrapped; + if (!jbn->db) { + work->rc = JN_ERROR_INVALID_STATE; + return; + } + struct JNRMC_DATA *wdata = work->data; + work->rc = ejdb_remove_collection(jbn->db, wdata->coll); +} + +static void jn_rmcoll_complete(napi_env env, napi_status ns, void *data) { + JNWORK work = data; + if (jn_resolve_pending_errors(env, ns, work)) { + goto finish; + } + JNGO(ns, env, napi_resolve_deferred(env, work->deferred, jn_undefined(env)), finish); + work->deferred = 0; + +finish: + jn_work_destroy(env, &work); +} + +static napi_value jn_rmcoll(napi_env env, napi_callback_info info) { + iwrc rc = 0; + napi_status ns; + napi_value this, argv; + napi_value ret = 0; + size_t argc = 1; + void *data; + + JNWORK work = jn_work_create(&rc); + RCGO(rc, finish); + + JNGO(ns, env, napi_get_cb_info(env, info, &argc, &argv, &this, &data), finish); + if (argc != 1) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + struct JNRMC_DATA *wdata = jn_work_alloc_data(sizeof(*wdata), work, &rc); + RCGO(rc, finish); + wdata->coll = jn_string(env, argv, work->pool, false, false, &rc); + RCGO(rc, finish); + + ret = jn_launch_promise(env, info, "rmcoll", jn_rmcoll_execute, jn_rmcoll_complete, work); + +finish: + if (rc) { + JNRC(env, rc); + if (work) { + jn_work_destroy(env, &work); + } + } + return ret ? ret : jn_undefined(env); +} + +// ---------------- EJDB2.onlineBackup + +struct JNBK_DATA { + uint64_t ts; + const char *file_name; +}; + +static void jn_online_backup_execute(napi_env env, void *data) { + JNWORK work = data; + JBN jbn = work->unwrapped; + if (!jbn->db) { + work->rc = JN_ERROR_INVALID_STATE; + return; + } + struct JNBK_DATA *wdata = work->data; + work->rc = ejdb_online_backup(jbn->db, &wdata->ts, wdata->file_name); +} + +static void jn_online_backup_complete(napi_env env, napi_status ns, void *data) { + napi_value rv; + JNWORK work = data; + if (jn_resolve_pending_errors(env, ns, work)) { + goto finish; + } + struct JNBK_DATA *wdata = work->data; + ns = napi_create_int64(env, (int64_t) wdata->ts, &rv); + if (ns) { + jn_resolve_pending_errors(env, ns, work); + goto finish; + } + JNGO(ns, env, napi_resolve_deferred(env, work->deferred, rv), finish); + work->deferred = 0; + +finish: + jn_work_destroy(env, &work); +} + +static napi_value jn_online_backup(napi_env env, napi_callback_info info) { + iwrc rc = 0; + napi_status ns = 0; + napi_value this, argv[1] = { 0 }; + size_t argc = sizeof(argv) / sizeof(argv[0]); + void *data; + napi_value ret = 0; + JNWORK work = jn_work_create(&rc); + RCGO(rc, finish); + + JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish); + if (argc != sizeof(argv) / sizeof(argv[0])) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + struct JNBK_DATA *wdata = jn_work_alloc_data(sizeof(*wdata), work, &rc); + RCGO(rc, finish); + wdata->file_name = jn_string(env, argv[0], work->pool, false, true, &rc); + ret = jn_launch_promise(env, info, "online_backup", jn_online_backup_execute, jn_online_backup_complete, work); + +finish: + if (rc) { + JNRC(env, rc); + if (work) { + jn_work_destroy(env, &work); + } + } + return ret ? ret : jn_undefined(env); +} + +// ---------------- jql_init + +typedef struct JNQL { + int refs; + JQL jql; +} *JNQL; + +static void jn_jnql_destroy_mt(JNQL *qlp) { + if (!qlp || !*qlp) { + return; + } + JNQL ql = *qlp; + if (ql->jql) { + jql_destroy(&ql->jql); + } + free(ql); +} + +static void jn_jql_finalize(napi_env env, void *data, void *hint) { + JNQL jnql = data; + if (jnql) { + jn_jnql_destroy_mt(&jnql); + } +} + +// JQL._impl.jql_init(this, query, collection); +static napi_value jn_jql_init(napi_env env, napi_callback_info info) { + iwrc rc = 0; + napi_status ns; + napi_value argv[3], this; + size_t argc = sizeof(argv) / sizeof(argv[0]); + void *data; + JNQL jnql = 0; + + IWPOOL *pool = iwpool_create(255); + if (!pool) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + jnql = calloc(1, sizeof(*jnql)); + if (!jnql) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + + JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish); + if (argc != sizeof(argv) / sizeof(argv[0])) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + + const char *query = jn_string(env, argv[1], pool, false, false, &rc); + RCGO(rc, finish); + + const char *collection = jn_string(env, argv[2], pool, true, false, &rc); + RCGO(rc, finish); + + rc = jql_create2(&jnql->jql, collection, query, JQL_KEEP_QUERY_ON_PARSE_ERROR | JQL_SILENT_ON_PARSE_ERROR); + RCGO(rc, finish); + + JNGO(ns, env, napi_wrap(env, argv[0], jnql, jn_jql_finalize, 0, 0), finish); + + napi_value vcoll = jn_create_string(env, jql_collection(jnql->jql)); + if (!vcoll) { + goto finish; + } + JNGO(ns, env, napi_set_named_property(env, argv[0], "collection", vcoll), finish); + +finish: + if (rc) { + if ((rc == JQL_ERROR_QUERY_PARSE) && jnql && jnql->jql) { + JNTHROW(env, rc, jql_error(jnql->jql)); + } else { + JNRC(env, rc); + } + if (jnql) { + jn_jnql_destroy_mt(&jnql); + } + } + iwpool_destroy(pool); + return jn_undefined(env); +} + +// ---------------- jql_stream_attach + +typedef struct JNQS { // query stream associated data + volatile bool aborted; + volatile bool paused; + int refs; + JBN jbn; + JNQL jnql; + napi_ref stream_ref; // Reference to the stream object + napi_ref explain_cb_ref; // Reference to the optional explain callback + int64_t limit; + struct JNWORK work; + pthread_mutex_t mtx; + pthread_cond_t cond; +} *JNQS; + +typedef struct JNCS { // call data to `k_add_stream_tsfn` + bool has_count; + IWXSTR *log; + IWXSTR *document; + int64_t count; + int64_t document_id; + napi_ref stream_ref; // copied from `JNQS` +} *JNCS; + +static void jn_cs_destroy(JNCS *csp) { + if (!csp || !*csp) { + return; + } + JNCS cs = *csp; + if (cs->document) { + iwxstr_destroy(cs->document); + } + if (cs->log) { + iwxstr_destroy(cs->log); + } + free(cs); + *csp = 0; +} + +// function addStreamResult(stream, id, jsondoc, log) +static void jn_resultset_tsf( + napi_env env, + napi_value js_add_stream, + void *context, + void *data) { + + if (!env) { // shutdown pending + JNCS cs = data; + jn_cs_destroy(&cs); + return; + } + + JNCS cs = data; + napi_status ns; + napi_value vstream, vid, vdoc, vlog, vresult; + napi_value vglobal = jn_global(env); + napi_value vnull = jn_null(env); + + if (!vglobal || !vnull) { + goto finish; + } + vstream = jn_get_ref(env, cs->stream_ref); + if (!vstream) { // exception pending + goto finish; + } + if (cs->document) { + vdoc = jn_create_string(env, iwxstr_ptr(cs->document)); + iwxstr_destroy(cs->document); + cs->document = 0; + } else if (cs->has_count) { + vdoc = jn_create_int64(env, cs->count); + } else { + vdoc = vnull; + } + if (!vdoc) { + goto finish; + } + vid = jn_create_int64(env, cs->document_id); + if (!vid) { + goto finish; + } + if (cs->log) { + vlog = jn_create_string(env, iwxstr_ptr(cs->log)); + } else { + vlog = vnull; + } + + napi_value argv[] = { vstream, vid, vdoc, vlog }; + const int argc = sizeof(argv) / sizeof(argv[0]); + JNGO(ns, env, napi_call_function( + env, + vglobal, + js_add_stream, + argc, + argv, + &vresult + ), finish); + +finish: + if (cs->document_id < 0) { + uint32_t refs; + napi_reference_unref(env, cs->stream_ref, &refs); + } + jn_cs_destroy(&cs); +} + +static void jn_jnqs_destroy_mt(napi_env env, JNQS *qsp) { + if (!qsp || !*qsp) { + return; + } + JNQS qs = *qsp; + uint32_t rcnt = 0; + if (--qs->refs > 0) { + return; + } + if (qs->jnql) { + qs->jnql->refs--; + } + if (qs->stream_ref) { + napi_reference_unref(env, qs->stream_ref, &rcnt); + if (!rcnt) { + napi_ref ref = qs->stream_ref; + qs->stream_ref = 0; + napi_delete_reference(env, ref); + } + } + if (qs->explain_cb_ref) { + napi_reference_unref(env, qs->explain_cb_ref, &rcnt); + if (!rcnt) { + napi_ref ref = qs->explain_cb_ref; + qs->explain_cb_ref = 0; + napi_delete_reference(env, ref); + } + } + free(qs); +} + +static iwrc jn_stream_pause_guard(JNQS qs) { + iwrc rc = 0; + int rci = pthread_mutex_lock(&qs->mtx); + if (rci) { + return iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci); + } + while (qs->paused) { + rci = pthread_cond_wait(&qs->cond, &qs->mtx); + if (rci) { + rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci); + break; + } + } + pthread_mutex_unlock(&qs->mtx); + return rc; +} + +static iwrc jn_jql_stream_visitor(EJDB_EXEC *ux, EJDB_DOC doc, int64_t *step) { + JNCS cs = 0; + JNQS qs = ux->opaque; + JNWORK work = &qs->work; + + if (qs->aborted) { + *step = 0; + return 0; + } + work->rc = jn_stream_pause_guard(qs); + RCRET(work->rc); + + IWXSTR *xstr = iwxstr_new(); + if (!xstr) { + work->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return work->rc; + } + if (doc->node) { + work->rc = jbn_as_json(doc->node, jbl_xstr_json_printer, xstr, 0); + } else { + work->rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + } + RCGO(work->rc, finish); + + cs = malloc(sizeof(*cs)); + if (!cs) { + work->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + cs->count = 0; + cs->has_count = false; + cs->stream_ref = qs->stream_ref; + cs->log = ux->log; + cs->document_id = doc->id; + cs->document = xstr; + + napi_status ns = napi_call_threadsafe_function(qs->jbn->resultset_tsf, cs, napi_tsfn_blocking); + if (ns) { + work->rc = JN_ERROR_NAPI; + work->ns = ns; + goto finish; + } + ux->log = 0; + +finish: + if (work->rc) { + iwxstr_destroy(xstr); + if (cs) { + cs->document = 0; // kept in xstr + jn_cs_destroy(&cs); + } + } + return work->rc; +} + +static void jn_jql_stream_execute(napi_env env, void *data) { + napi_status ns; + uint32_t refs = 0; + JNWORK work = data; + JNQS qs = work->data; + JQL q = qs->jnql->jql; + JNCS cs = 0; + EJDB_EXEC ux = { 0 }; + bool has_count = jql_has_aggregate_count(q); + + // Trying to stop on paused stream + // not in context of query execution + // hence we can avoid unnecessary database locking + // before start reading stream. + work->rc = jn_stream_pause_guard(qs); + RCGO(work->rc, finish); + JNGO(ns, env, napi_reference_ref(env, qs->stream_ref, &refs), finish); + + if (qs->explain_cb_ref) { + ux.log = iwxstr_new(); + if (!ux.log) { + work->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + } + + ux.q = q; + ux.db = qs->jbn->db; + ux.opaque = qs; + ux.limit = qs->limit; + if (!has_count) { + ux.visitor = jn_jql_stream_visitor; + } + + work->rc = ejdb_exec(&ux); + RCGO(work->rc, finish); + + // Stream close event + cs = malloc(sizeof(*cs)); + if (!cs) { + work->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + cs->has_count = has_count; + cs->count = ux.cnt; + cs->stream_ref = qs->stream_ref; + cs->log = ux.log; + cs->document_id = -1; + cs->document = 0; + ux.log = 0; + + ns = napi_call_threadsafe_function(qs->jbn->resultset_tsf, cs, napi_tsfn_blocking); + if (ns) { + work->rc = JN_ERROR_NAPI; + work->ns = ns; + goto finish; + } + + refs = 0; + +finish: + if (refs) { + napi_reference_unref(env, qs->stream_ref, &refs); + } + if (ux.log) { + iwxstr_destroy(ux.log); + } + if (work->rc) { + jn_cs_destroy(&cs); + } +} + +static void jn_jql_stream_complete(napi_env env, napi_status ns, void *data) { + JNWORK work = data; + JNQS qs = work->data; + if (!ns) { + ns = work->ns; + } + if (jn_resolve_pending_errors(env, ns, work)) { + goto finish; + } + JNGO(ns, env, napi_resolve_deferred(env, work->deferred, jn_undefined(env)), finish); + work->deferred = 0; + +finish: + jn_work_destroy(env, &work); + jn_jnqs_destroy_mt(env, &qs); +} + +// this.jql._impl.jql_stream_destroy(this); +static napi_value jn_jql_stream_destroy(napi_env env, napi_callback_info info) { + void *data; + size_t argc = 1; + napi_value ret = jn_undefined(env), argv, this; + + napi_status ns = napi_get_cb_info(env, info, &argc, &argv, &this, &data); + if (ns || (argc < 1)) { + goto finish; + } + + ns = napi_remove_wrap(env, argv, &data); + if (ns || !data) { + goto finish; + } + + JNQS qs = data; + jn_jnqs_destroy_mt(env, &qs); + +finish: + return ret; +} + +static napi_value jn_jql_stream_set_paused(napi_env env, napi_callback_info info, bool paused) { + napi_status ns; + napi_value argv, this; + size_t argc = 1; + void *data; + + JNGO(ns, env, napi_get_cb_info(env, info, &argc, &argv, &this, &data), finish); + if (argc != 1) { + iwrc rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + JNRC(env, rc); + goto finish; + } + JNGO(ns, env, napi_unwrap(env, argv, &data), finish); + JNQS qs = data; + assert(qs); + + int rci = pthread_mutex_lock(&qs->mtx); + if (rci) { + iwrc rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci); + JNRC(env, rc); + goto finish; + } + if (qs->paused != paused) { + qs->paused = paused; + pthread_cond_broadcast(&qs->cond); + } + pthread_mutex_unlock(&qs->mtx); + +finish: + return jn_undefined(env); +} + +static napi_value jn_jql_stream_pause(napi_env env, napi_callback_info info) { + return jn_jql_stream_set_paused(env, info, true); +} + +static napi_value jn_jql_stream_resume(napi_env env, napi_callback_info info) { + return jn_jql_stream_set_paused(env, info, false); +} + +static napi_value jn_jql_stream_abort(napi_env env, napi_callback_info info) { + napi_status ns; + napi_value argv, this; + size_t argc = 1; + void *data; + JNGO(ns, env, napi_get_cb_info(env, info, &argc, &argv, &this, &data), finish); + if (argc != 1) { + iwrc rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + JNRC(env, rc); + goto finish; + } + JNGO(ns, env, napi_unwrap(env, argv, &data), finish); + JNQS qs = data; + qs->aborted = true; +finish: + return jn_undefined(env); +} + +// JQL._impl.jql_stream_attach(this, stream, [opts.limit, opts.explainCallback]); +static napi_value jn_jql_stream_attach(napi_env env, napi_callback_info info) { + iwrc rc = 0; + napi_status ns; + napi_value ret = 0, argv[3], this, vexplain; + size_t argc = sizeof(argv) / sizeof(argv[0]); + void *data; + + JBN jbn; + JNQL jnql; + JNQS qs = 0; + + JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish); + if (argc != sizeof(argv) / sizeof(argv[0])) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + JNGO(ns, env, napi_unwrap(env, argv[0], (void**) &jnql), finish); // -V580 + + qs = calloc(1, sizeof(*qs)); + if (!qs) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + + pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; + pthread_cond_t cond = PTHREAD_COND_INITIALIZER; + memcpy(&qs->mtx, &mtx, sizeof(mtx)); + memcpy(&qs->cond, &cond, sizeof(cond)); + + qs->paused = true; + qs->jnql = jnql; + qs->jnql->refs++; // Query in use + qs->limit = jn_int_at(env, argv[2], true, false, 0, &rc); + RCGO(rc, finish); + + JNGO(ns, env, napi_get_element(env, argv[2], 1, &vexplain), finish); + if (!jn_is_null_or_undefined(env, vexplain)) { + napi_valuetype vtype; + JNGO(ns, env, napi_typeof(env, vexplain, &vtype), finish); + if (vtype != napi_function) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + JNGO(ns, env, napi_create_reference(env, vexplain, 1, &qs->explain_cb_ref), finish); + } + JNGO(ns, env, napi_unwrap(env, this, (void**) &jbn), finish); // -V580 + JNGO(ns, env, napi_create_reference(env, argv[1], 1, &qs->stream_ref), finish); // Reference to stream + JNGO(ns, env, napi_wrap(env, argv[1], qs, 0, 0, 0), finish); + + // Launch async work + qs->refs = 2; // +1 for jn_jql_stream_complete + // +1 for jn_jql_stream_destroy + qs->jbn = jbn; + qs->work.data = qs; + ret = jn_launch_promise(env, info, "query", jn_jql_stream_execute, jn_jql_stream_complete, &qs->work); + +finish: + if (rc) { + JNRC(env, rc); + if (qs) { + qs->refs = 0; // needed to destroy it completely + } + jn_jnqs_destroy_mt(env, &qs); + } + return ret ? ret : jn_undefined(env); +} + +static void jn_jql_free_set_string_value(void *ptr, void *op) { + IWPOOL *pool = op; + if (pool) { + iwpool_destroy(pool); + } +} + +// this._impl.jql_set(jql, placeholder, val, 1); +static napi_value jn_jql_set(napi_env env, napi_callback_info info) { + iwrc rc = 0; + int iplh = 0; + napi_value argv[4], this; + size_t argc = sizeof(argv) / sizeof(argv[0]); + const char *splh = 0, *svalue; + JNQL jnql; + void *data; + napi_status ns; + napi_valuetype vtype; + + IWPOOL *vpool = 0; // jql_set_xxx value + IWPOOL *pool = iwpool_create(32); + if (!pool) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + + JNGO(ns, env, napi_get_cb_info(env, info, &argc, argv, &this, &data), finish); + if (argc != sizeof(argv) / sizeof(argv[0])) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + JNGO(ns, env, napi_unwrap(env, argv[0], &data), finish); + jnql = data; + if (jnql->refs > 0) { + rc = JN_ERROR_QUERY_IN_USE; + goto finish; + } + + // Set type + int stype = jn_int(env, argv[3], false, false, &rc); + RCGO(rc, finish); + + // Placeholder + JNGO(ns, env, napi_typeof(env, argv[1], &vtype), finish); + + if (vtype == napi_string) { + iplh = 0; + splh = jn_string(env, argv[1], pool, false, false, &rc); + } else if (vtype == napi_number) { + splh = 0; + iplh = jn_int(env, argv[1], false, false, &rc); + } else { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + } + RCGO(rc, finish); + + switch (stype) { + case 6: // String + case 1: // JSON + case 2: { // Regexp + JBL_NODE node; + vpool = iwpool_create(64); + if (!vpool) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + RCGO(rc, finish); + switch (stype) { + case 6: // String + case 2: // Regexp + svalue = jn_string(env, argv[2], vpool, false, false, &rc); + RCGO(rc, finish); + if (stype == 6) { + rc = jql_set_str2(jnql->jql, splh, iplh, svalue, jn_jql_free_set_string_value, vpool); // -V614 + } else { + rc = jql_set_regexp2(jnql->jql, splh, iplh, svalue, jn_jql_free_set_string_value, vpool); + } + RCGO(rc, finish); + break; + case 1: + svalue = jn_string(env, argv[2], pool, false, false, &rc); + RCGO(rc, finish); + rc = jbn_from_json(svalue, &node, vpool); + RCGO(rc, finish); + rc = jql_set_json2(jnql->jql, splh, iplh, node, jn_jql_free_set_string_value, vpool); + RCGO(rc, finish); + break; + } + } + break; + + case 3: { // Integer + int64_t v = jn_int(env, argv[2], false, false, &rc); + RCGO(rc, finish); + rc = jql_set_i64(jnql->jql, splh, iplh, v); + RCGO(rc, finish); + } + break; + + case 4: { // Double + double v = jn_double(env, argv[2], false, false, &rc); + RCGO(rc, finish); + rc = jql_set_f64(jnql->jql, splh, iplh, v); + RCGO(rc, finish); + } + break; + + case 5: { // Boolean + bool v = jn_bool(env, argv[2], false, false, &rc); + RCGO(rc, finish); + rc = jql_set_bool(jnql->jql, splh, iplh, v); + RCGO(rc, finish); + } + break; + } + +finish: + if (pool) { + iwpool_destroy(pool); + } + if (rc) { + if (vpool) { // vpool will be destroyed when JQL freed + iwpool_destroy(vpool); + } + JNRC(env, rc); + } + return jn_undefined(env); +} + +// jql_limit(jql); +static napi_value jn_jql_limit(napi_env env, napi_callback_info info) { + iwrc rc = 0; + napi_status ns; + napi_value ret = 0, argv, this; + size_t argc = 1; + void *data; + JNQL jnql; + int64_t limit; + + JNGO(ns, env, napi_get_cb_info(env, info, &argc, &argv, &this, &data), finish); + if (argc != 1) { + rc = JN_ERROR_INVALID_NATIVE_CALL_ARGS; + goto finish; + } + JNGO(ns, env, napi_unwrap(env, argv, &data), finish); + jnql = data; + + rc = jql_get_limit(jnql->jql, &limit); + RCGO(rc, finish); + + ret = jn_create_int64(env, limit); + +finish: + if (rc) { + JNRC(env, rc); + } + return ret ? ret : jn_undefined(env); +} + +// ---------------- + +static const char *jn_ecodefn(locale_t locale, uint32_t ecode) { + if (!((ecode > _JN_ERROR_START) && (ecode < _JN_ERROR_END))) { + return 0; + } + switch (ecode) { + case JN_ERROR_INVALID_NATIVE_CALL_ARGS: + return "Invalid native function call args (JN_ERROR_INVALID_NATIVE_CALL_ARGS)"; + case JN_ERROR_INVALID_STATE: + return "Invalid native extension state (JN_ERROR_INVALID_STATE)"; + case JN_ERROR_QUERY_IN_USE: + return "Query object is in-use by active async iteration, and cannot be changed (JN_ERROR_QUERY_IN_USE)"; + case JN_ERROR_NAPI: + return "N-API Error (JN_ERROR_NAPI)"; + } + return 0; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_status ns = 0; + static volatile int jn_ecodefn_initialized = 0; + if (__sync_bool_compare_and_swap(&jn_ecodefn_initialized, 0, 1)) { + iwrc rc = ejdb_init(); + if (rc) { + JNRC(env, rc); + return 0; + } + iwlog_register_ecodefn(jn_ecodefn); + } + napi_value vglobal, vadd_streamfn; + + JNGO(ns, env, napi_get_global(env, &vglobal), finish); + + // Define EJDB2Impl class + napi_value ejdb2impl_clazz; + napi_property_descriptor properties[] = { + JNFUNC(open), + JNFUNC(close), + JNFUNC(put), + JNFUNC(patch), + JNFUNC(patch_or_put), + JNFUNC(get), + JNFUNC(del), + JNFUNC(rename_collection), + JNFUNC(info), + JNFUNC(index), + JNFUNC(rmcoll), + JNFUNC(online_backup), + JNFUNC(jql_init), + JNFUNC(jql_set), + JNFUNC(jql_limit), + JNFUNC(jql_stream_destroy), + JNFUNC(jql_stream_attach), + JNFUNC(jql_stream_pause), + JNFUNC(jql_stream_resume), + JNFUNC(jql_stream_abort) + }; + JNGO(ns, env, napi_define_class(env, "EJDB2Impl", NAPI_AUTO_LENGTH, jn_ejdb2impl_ctor, 0, + sizeof(properties) / sizeof(properties[0]), + properties, &ejdb2impl_clazz), finish); + + napi_property_descriptor descriptors[] = { + { "EJDB2Impl", 0, 0, 0, 0, ejdb2impl_clazz, napi_default, 0 } + }; + JNGO(ns, env, napi_define_properties(env, exports, + sizeof(descriptors) / sizeof(descriptors[0]), + descriptors), + finish); + + JNGO(ns, env, napi_get_named_property(env, vglobal, "__ejdb_add_stream_result__", &vadd_streamfn), finish); + if (jn_is_null_or_undefined(env, vadd_streamfn)) { + iwrc rc = JN_ERROR_INVALID_STATE; + JNRC(env, rc); + goto finish; + } + JNGO(ns, env, napi_create_reference(env, vadd_streamfn, 1, &k_vadd_streamfn_ref), finish); + +finish: + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/bindings/ejdb2_node/example/README.md b/src/bindings/ejdb2_node/example/README.md new file mode 100644 index 0000000..566fd95 --- /dev/null +++ b/src/bindings/ejdb2_node/example/README.md @@ -0,0 +1,33 @@ +# EJDB2 Typescript example project + +```ts +import { EJDB2 } from 'ejdb2_node'; + +async function run() { + const db = await EJDB2.open('example.db', { truncate: true }); + + var id = await db.put('parrots', {'name': 'Bianca', 'age': 4}); + console.log(`Bianca record: ${id}`); + + id = await db.put('parrots', {'name': 'Darko', 'age': 8}); + console.log(`Darko record: ${id}`); + + const q = db.createQuery('/[age > :age]', 'parrots'); + + for await (const doc of q.setNumber('age', 3).stream()) { + console.log(`Found ${doc}`); + } + + await db.close(); +} + +run(); +``` + +## Build and run + +```sh +cd ./example +yarn install +yarn run start +``` \ No newline at end of file diff --git a/src/bindings/ejdb2_node/example/index.ts b/src/bindings/ejdb2_node/example/index.ts new file mode 100644 index 0000000..0cfc096 --- /dev/null +++ b/src/bindings/ejdb2_node/example/index.ts @@ -0,0 +1,21 @@ +import { EJDB2 } from 'ejdb2_node'; + +async function run() { + const db = await EJDB2.open('example.db', { truncate: true }); + + var id = await db.put('parrots', {'name': 'Bianca', 'age': 4}); + console.log(`Bianca record: ${id}`); + + id = await db.put('parrots', {'name': 'Darko', 'age': 8}); + console.log(`Darko record: ${id}`); + + const q = db.createQuery('/[age > :age]', 'parrots'); + + for await (const doc of q.setNumber('age', 3).stream()) { + console.log(`Found ${doc}`); + } + + await db.close(); +} + +run(); diff --git a/src/bindings/ejdb2_node/example/package.json b/src/bindings/ejdb2_node/example/package.json new file mode 100644 index 0000000..ee8cb9e --- /dev/null +++ b/src/bindings/ejdb2_node/example/package.json @@ -0,0 +1,17 @@ +{ + "name": "ejdb2_ts_example", + "version": "1.0.0", + "main": "index.js", + "description": "EJDB2 TypeScript example project", + "private": true, + "scripts": { + "start": "tsc && node index.js" + }, + "dependencies": { + "ejdb2_node": "^1.0.23", + "typescript": "^3.5.2" + }, + "devDependencies": { + "@types/node": "^12.12.5" + } +} \ No newline at end of file diff --git a/src/bindings/ejdb2_node/example/tsconfig.json b/src/bindings/ejdb2_node/example/tsconfig.json new file mode 100644 index 0000000..e63a31d --- /dev/null +++ b/src/bindings/ejdb2_node/example/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es6", + "moduleResolution": "node", + "module": "commonjs", + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "declaration": false, + "pretty": true, + "lib": [ + "es6" + ], + "baseUrl": "." + }, + "exclude": [ + "node_modules/**", + "**/*.aot.ts" + ] +} \ No newline at end of file diff --git a/src/bindings/ejdb2_node/hints.txt b/src/bindings/ejdb2_node/hints.txt new file mode 100644 index 0000000..6094ce5 --- /dev/null +++ b/src/bindings/ejdb2_node/hints.txt @@ -0,0 +1,2 @@ +gdb: set follow-fork-mode child +valgrind --leak-check=full --trace-children=yes node --expose-gc ./node_modules/.bin/ava -s -c 0 diff --git a/src/bindings/ejdb2_node/index.d.ts b/src/bindings/ejdb2_node/index.d.ts new file mode 100644 index 0000000..a02868c --- /dev/null +++ b/src/bindings/ejdb2_node/index.d.ts @@ -0,0 +1,462 @@ +/************************************************************************************************** + * EJDB2 + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +/// + +declare namespace ejdb2_node { + export type Placeholder = string | number; + + /** + * EJDB2 Error helpers. + */ + export class JBE { + /** + * Returns `true` if given error [err] is `IWKV_ERROR_NOTFOUND` + * @param err + */ + static isNotFound(err: any): boolean; + + /** + * Returns `true` if given errror [err] is `JQL_ERROR_QUERY_PARSE` + * @param {Error} err + * @return {boolean} + */ + static isInvalidQuery(err: any): boolean; + } + + /** + * EJDB document. + */ + interface JBDOC { + /** + * Document identifier + */ + id: number; + + /** + * Document JSON object + */ + json: any; + + /** + * String represen + */ + toString(): string; + } + + interface JBDOCStream { + [Symbol.asyncIterator](): AsyncIterableIterator; + } + + /** + * Query execution options. + */ + interface QueryOptions { + /** + * Overrides `limit` encoded in query text + */ + limit?: number; + + /** + * Calback used to get query execution log. + */ + explainCallback?: (log: string) => void; + } + + /** + * EJDB Query. + */ + class JQL { + /** + * Database to which query attached. + */ + readonly db: EJDB2; + + /** + * Get `limit` value used by query. + */ + readonly limit: number; + + /** + * Executes a query and returns a + * readable stream of matched documents. + * + */ + stream(opts?: QueryOptions): JBDOCStream; + + /** + * Executes this query and waits its completion. + */ + completionPromise(opts?: QueryOptions): Promise; + + /** + * Returns a scalar integer value as result of query execution. + * Eg.: A count query: `/... | count` + */ + scalarInt(opts?: QueryOptions): Promise; + + /** + * Returns result set as a list. + * Use it with caution on large data sets. + */ + list(opts?: QueryOptions): Promise>; + + /** + * Collects up to [n] documents from result set into array. + */ + firstN(n: number, opts?: QueryOptions): Promise>; + + /** + * Returns a first record in result set. + * If record is not found promise with `undefined` will be returned. + */ + first(opts?: QueryOptions): Promise; + + /** + * Set [json] at the specified [placeholder]. + */ + setJSON(placeholder: Placeholder, val: object | string): JQL; + + /** + * Set [regexp] string at the specified [placeholder]. + */ + setRegexp(placeholder: Placeholder, val: string): JQL; + + /** + * Set number [val] at the specified [placeholder]. + */ + setNumber(placeholder: Placeholder, val: number): JQL; + + /** + * Set boolean [val] at the specified [placeholder]. + */ + setBoolean(placeholder: Placeholder, val: boolean): JQL; + + /** + * Set string [val] at the specified [placeholder]. + */ + setString(placeholder: Placeholder, val: string): JQL; + + /** + * Set `null` at the specified [placeholder]. + */ + setNull(placeholder: Placeholder): JQL; + } + + interface OpenOptions { + /** + * Open databas in read-only mode. + */ + readonly?: boolean; + + /** + * Truncate database file on open. + */ + truncate?: boolean; + + /** + * Enable WAL. Default: true. + */ + wal_enabled?: boolean; + + /** + * Check CRC32 sum for every WAL checkpoint. + * Default: false. + */ + wal_check_crc_on_checkpoint?: boolean; + + /** + * Size of checkpoint buffer in bytes. + * Default: 1Gb. Android: 64Mb + */ + wal_checkpoint_buffer_sz?: number; + + /** + * WAL buffer size in bytes. + * Default: 8Mb. Android: 2Mb. + */ + wal_wal_buffer_sz?: number; + + /** + * Checkpoint timeout in seconds. + * Default: 300. Android: 60. + */ + wal_checkpoint_timeout_sec?: number; + + /** + * Savepoint timeout in secods. + * Default: 10. + */ + wal_savepoint_timeout_sec?: number; + + /** + * Initial size of buffer in bytes used to process/store document on queries. + * Preferable average size of document. + * Default: 65536. Minimal: 16384. + */ + document_buffer_sz?: number; + + /** + * Max sorting buffer size in bytes. + * If exceeded, an overflow temp file for data will be created. + * Default: 16777216. Minimal: 1048576 + */ + sort_buffer_sz?: number; + + /** + * Enable HTTP/Websocket endpoint. + */ + http_enabled?: boolean; + + /** + * Server access token matched to 'X-Access-Token' HTTP header value. + */ + http_access_token?: string; + + /** + * Server ip address to bind. + */ + http_bind?: string; + + /** + * Max HTTP/WS API document body size. + * Default: 67108864. Minimal: 524288. + */ + http_max_body_size?: number; + + /** + * HTTP port to listen. + */ + http_port?: number; + + /** + * Allow anonymous read request. + */ + http_read_anon?: boolean; + } + + /** + * Collection index descriptor. + */ + interface IndexDescriptor { + /** + * rfc6901 JSON pointer to indexed field. + */ + ptr: string; + + /** + * Index mode as bit mask: + * + * - 0x01 EJDB_IDX_UNIQUE Index is unique + * - 0x04 EJDB_IDX_STR Index for JSON string field value type + * - 0x08 EJDB_IDX_I64 Index for 8 bytes width signed integer field values + * - 0x10 EJDB_IDX_F64 Index for 8 bytes width signed floating point field values. + */ + mode: number; + + /** + * Index flags. See iwkv.h#iwdb_flags_t + */ + idbf: number; + + /** + * Internal index database identifier. + */ + dbid: number; + + /** + * Number of indexed records. + */ + rnum: number; + } + + /** + * Collection descriptor. + */ + interface CollectionDescriptor { + /** + * Name of collection. + */ + name: string; + + /** + * Internal database identifier. + */ + dbid: number; + + /** + * Number of documents stored in collection. + */ + rnum: number; + + /** + * List of collection indexes. + */ + indexes: Array; + } + + interface EJDB2Info { + /** + * Database engine version string. + * Eg. "2.0.29" + */ + version: string; + + /** + * Database file path. + */ + file: string; + + /** + * Database file size in bytes. + */ + size: number; + + /** + * List of database collections. + */ + collections: Array; + } + + /** + * EJDB2 Node.js wrapper. + */ + export class EJDB2 { + /** + * Open databse instance. + * @param path Database file path + * @param opts Options + */ + static open(path: String, opts?: OpenOptions): Promise; + + /** + * Closes this database instance. + */ + close(): Promise; + + /** + * Saves [json] document under specified [id] or create a document + * with new generated `id`. Returns promise holding actual document `id`. + */ + put(collection: String, json: object | string, id?: number): Promise; + + /** + * Apply rfc6902/rfc7386 JSON [patch] to the document identified by [id]. + */ + patch(collection: string, json: object | string, id: number): Promise; + + /** + * Apply JSON merge patch (rfc7396) to the document identified by `id` or + * insert new document under specified `id`. + */ + patchOrPut(collection: string, json: object | string, id: number): Promise; + + /** + * Get json body of document identified by [id] and stored in [collection]. + * + * If document with given `id` is not found then `Error` will be thrown. + * Not found error can be detected by {@link JBE.isNotFound} + */ + get(collection: string, id: number): Promise; + + /** + * Get json body of document identified by [id] and stored in [collection]. + * + * If document with given `id` is not found then `null` will be resoved. + */ + getOrNull(collection: string, id: number): Promise; + + /** + * Get json body with database metadata. + */ + info(): Promise; + + /** + * Removes document idenfied by [id] from [collection]. + * + * If document with given `id` is not found then `Error` will be thrown. + * Not found error can be detected by {@link JBE.isNotFound} + */ + del(collection: string, id: number): Promise; + + /** + * Renames collection + */ + renameCollection(oldCollectionName: string, newCollectionName: string): Promise; + + /** + * Ensures json document database index specified by [path] json pointer to string data type. + */ + ensureStringIndex(collection: string, path: string, unique?: boolean): Promise; + + /** + * Removes specified database index. + */ + removeStringIndex(collection: string, path: string, unique?: boolean): Promise; + + /** + * Ensures json document database index specified by [path] json pointer to integer data type. + */ + ensureIntIndex(collection: string, path: string, unique?: boolean): Promise; + + /** + * Removes specified database index. + */ + removeIntIndex(collection: string, path: string, unique?: boolean): Promise; + + /** + * Ensures json document database index specified by [path] json pointer to floating point data type. + */ + ensureFloatIndex(collection: string, path: string, unique?: boolean): Promise; + + /** + * Removes specified database index. + */ + removeFloatIndex(collection: string, path: string, unique?: boolean): Promise; + + /** + * Removes database [collection]. + */ + removeCollection(collection: string): Promise; + + /** + * Create instance of [query] specified for [collection]. + * If [collection] is not specified a [query] spec must contain collection name, + * eg: `@mycollection/[foo=bar]` + */ + createQuery(query: string, collection?: string): JQL; + + /** + * Creates an online database backup image and copies it into the specified [fileName]. + * During online backup phase read/write database operations are allowed and not + * blocked for significant amount of time. Returns promise with backup + * finish time as number of milliseconds since epoch. + */ + onlineBackup(fileName: string): Promise; + } +} + +export = ejdb2_node; diff --git a/src/bindings/ejdb2_node/index.js b/src/bindings/ejdb2_node/index.js new file mode 100644 index 0000000..b0d172b --- /dev/null +++ b/src/bindings/ejdb2_node/index.js @@ -0,0 +1,721 @@ +/************************************************************************************************** + * EJDB2 Node.js native API binding. + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +const semver = require('semver'); +const {engines} = require('./package'); + +if (!semver.satisfies(process.version, engines.node)) { + console.log(`Required node version ${engines.node} not satisfied with current version ${process.version}.`); + process.exit(1); +} + +global.__ejdb_add_stream_result__ = addStreamResult; // Passing it to ejdb2_node init +const {EJDB2Impl} = require('./binary')('ejdb2_node'); +const {Readable} = require('stream'); +delete global.__ejdb_add_stream_result__; + +/** + * EJDB2 Error helpers. + */ +class JBE { + + /** + * Returns `true` if given error [err] is `IWKV_ERROR_NOTFOUND` + * @param {Error} err + * @returns {boolean} + */ + static isNotFound(err) { + const code = (err.code || '').toString(); + return code.indexOf('@ejdb IWRC:75001') == 0; + } + + /** + * Returns `true` if given errror [err] is `JQL_ERROR_QUERY_PARSE` + * @param {Error} err + * @return {boolean} + */ + static isInvalidQuery(err) { + const code = (err.code || '').toString(); + return code.indexOf('@ejdb IWRC:87001') == 0; + } +} + +/** + * EJDB document. + */ +class JBDOC { + + /** + * Get document JSON object + */ + get json() { + if (this._json != null) { + return this._json; + } + this._json = JSON.parse(this._raw); + this._raw = null; + return this._json; + } + + /** + * @param {number} id Document ID + * @param {string} raw Document JSON as string + */ + constructor(id, raw) { + this.id = id; + this._raw = raw; + this._json = null; + } + + toString() { + return `JBDOC: ${this.id} ${this._raw != null ? this._raw : JSON.stringify(this.json)}`; + } +} + +/** + * EJDB Query resultset stream. + */ +class JBDOCStream extends Readable { + + get _impl() { + return this.jql._impl; + } + + get writable() { + return !this._paused && !this._destroyed; + } + + /** + * Query result stream. + * @param {JQL} jql + */ + constructor(jql, opts) { + super({ + objectMode: true, + highWaterMark: 64 + }); + this._pending = []; + this._paused = true; + this._destroyed = false; + this._aborted = false; + this.jql = jql; + this.opts = opts; + this.promise = this._impl.jql_stream_attach(jql, this, [opts.limit, opts.explainCallback]) + .catch((err) => this.destroy(err)); + } + + abort() { + // unpause if paused + // needed to release frozen on pause db query thread + this._doResume(); + // set internal abort state + this._impl.jql_stream_abort(this); + this._aborted = true; + } + + /** + * Destroy stream + */ + _destroy(err, callback) { + if (!this._destroyed) { + this.abort(); + this._destroyed = true; + setImmediate(() => this._impl.jql_stream_destroy(this)); + } + callback(err); + } + + _read() { + if (this._destroyed) { + return; + } + this._doResume(); + if (this._pending.length > 0) { + // Flush pending records to consumer + // Maintaining pending list since `napi_threadsafe_function` queue + // may be not empty at the time of `jql_stream_pause` call + let pv = this._pending.shift(); + while (this.writable && pv) { + if (pv.length == 0) { // Got pending EOF + this._pending.length = 0; + this.destroy(); + break; + } + addStreamResult(this, pv[0], pv[1]); + pv = this._pending.shift(); + } + } + } + + _doResume() { + if (this._paused) { + this._impl.jql_stream_resume(this); + this._paused = false; + } + } + + _doPause() { + if (!this._paused) { + this._impl.jql_stream_pause(this); + this._paused = true; + } + } +} + +// Global module function for add results to query stream +function addStreamResult(stream, id, jsondoc, log) { + if (stream._destroyed) { + return; + } + if (log != null && stream.opts.explainCallback != null) { + stream.opts.explainCallback(log); + delete stream.opts.explainCallback; + } + + const count = (typeof jsondoc === 'number'); + if (id >= 0 || count) { + if (!stream._aborted) { + if (stream._paused) { + // Maintaining pending list since `napi_threadsafe_function` queue + // may be not empty at the time of `jql_stream_pause` call + stream.pending.push([id, jsondoc]); + return; + } + let doc; + if (count) { // count int response + doc = new JBDOC(jsondoc, jsondoc); + } else if (jsondoc != null) { + doc = new JBDOC(id, jsondoc); + } + if (doc != null && stream.push(doc) == false) { + stream._doPause(); + } + } + } + + if (id < 0) { // last record + if (!stream._aborted && stream._paused) { + stream.pending.push([]); + } else { + stream.destroy(); + } + } +} + +/** + * EJDB Query. + */ +class JQL { + + get _impl() { + return this.db._impl; + } + + /** + * Get `limit` value used by query. + */ + get limit() { + return this._impl.jql_limit(this); + } + + /** + * @param {EJDB2} db + * @param {string} query + * @param {string} collection + */ + constructor(db, query, collection) { + this.db = db; + this.query = query; + this.collection = collection; + this._impl.jql_init(this, query, collection); + } + + /** + * Executes a query and returns a + * readable stream of matched documents. + * + * @param {Object} [opts] + * @return {ReadableStream} + */ + stream(opts) { + return new JBDOCStream(this, opts || {}); + } + + /** + * Executes this query and waits its completion. + * + * @param {Promise} opts + */ + completionPromise(opts) { + const stream = this.stream(opts || {}); + return new Promise((resolve, reject) => { + stream.on('data', () => stream.destroy()); + stream.on('close', () => resolve()); + stream.on('error', (err) => reject(err)); + }); + } + + /** + * Returns a scalar integer value as result of query execution. + * Eg.: A count query: `/... | count` + * @param {Object} [opts] + * @return {Promise} + */ + scalarInt(opts) { + const stream = this.stream(opts); + return new Promise((resolve, reject) => { + stream.on('data', (doc) => { + resolve(doc.id); + stream.destroy(); + }); + stream.on('error', (err) => reject(err)); + }); + } + + /** + * Returns result set as a list. + * Use it with caution on large data sets. + * + * @param {Object} [opts] + * @return {Promise>} + */ + list(opts) { + const ret = []; + const stream = this.stream(opts); + return new Promise((resolve, reject) => { + stream.on('data', (doc) => ret.push(doc)); + stream.on('close', () => resolve(ret)); + stream.on('error', (err) => reject(err)); + }); + } + + /** + * Collects up to [n] documents from result set into array. + * @param {number} n + * @param {Object} [opts] + * @return {Promise>} + */ + firstN(n, opts) { + opts = opts || {}; + opts.limit = n; + const ret = []; + const stream = this.stream(opts); + return new Promise((resolve, reject) => { + stream.on('data', (doc) => { + ret.push(doc); + if (ret.length >= n) { + stream.destroy(); + } + }); + stream.on('close', () => resolve(ret)); + stream.on('error', (err) => reject(err)); + }); + } + + /** + * Returns a first record in result set. + * If record is not found promise with `undefined` will be returned. + * + * @param {Object} [opts] + * @return {Promise} + */ + async first(opts) { + const fv = await this.firstN(1, opts); + return fv[0]; + } + + /** + * Set [json] at the specified [placeholder]. + * @param {string|number} placeholder + * @param {string|object} val + * @return {JQL} + */ + setJSON(placeholder, val) { + this._checkPlaceholder(placeholder); + if (typeof val !== 'string') { + val = JSON.stringify(val); + } + this._impl.jql_set(this, placeholder, val, 1); + return this; + } + + /** + * Set [regexp] string at the specified [placeholder]. + * @param {string|number} placeholder + * @param {string|RegExp} val + * @return {JQL} + */ + setRegexp(placeholder, val) { + this._checkPlaceholder(placeholder); + if (val instanceof RegExp) { + const sval = val.toString(); + val = sval.substring(1, sval.lastIndexOf('/')); + } else if (typeof val !== 'string') { + throw new Error('Regexp argument must be a string or RegExp object'); + } + this._impl.jql_set(this, placeholder, val, 2); + return this; + } + + /** + * Set number [val] at the specified [placeholder]. + * @param {string|number} placeholder + * @param {number} val + * @return {JQL} + */ + setNumber(placeholder, val) { + this._checkPlaceholder(placeholder); + if (typeof val !== 'number') { + throw new Error('Value must be a number'); + } + this._impl.jql_set(this, placeholder, val, this._isInteger(val) ? 3 : 4); + return this; + } + + /** + * Set boolean [val] at the specified [placeholder]. + * @param {string|number} placeholder + * @param {boolean} val + * @return {JQL} + */ + setBoolean(placeholder, val) { + this._checkPlaceholder(placeholder); + this._impl.jql_set(this, placeholder, !!val, 5); + return this; + } + + /** + * Set string [val] at the specified [placeholder]. + * @param {string|number} placeholder + * @param {string} val + * @return {JQL} + */ + setString(placeholder, val) { + this._checkPlaceholder(placeholder); + if (val != null && typeof val !== 'string') { + val = val.toString(); + } + this._impl.jql_set(this, placeholder, val, 6); + return this; + } + + /** + * Set `null` at the specified [placeholder]. + * @param {string|number} placeholder + * @return {JQL} + */ + setNull(placeholder) { + this._checkPlaceholder(placeholder); + this._impl.jql_set(this, placeholder, null, 7); + return this; + } + + _isInteger(n) { + return n === +n && n === (n | 0); + } + + _checkPlaceholder(placeholder) { + const t = typeof placeholder; + if (t !== 'number' && t !== 'string') { + throw new Error('Invalid placeholder specified, must be either string or number'); + } + } +} + +/** + * EJDB2 Nodejs wrapper. + */ +class EJDB2 { + + /** + * Open database instance. + * + * @param {String} path Path to database + * @param {Object} [opts] + * @returns {Promise} EJDB2 instance promise + */ + static open(path, opts) { + opts = opts || {}; + + function toArgs() { + let oflags = 0; + const ret = [path]; + if (opts['readonly']) { + oflags |= 0x02; + } + if (opts['truncate']) { + oflags |= 0x04; + } + ret.push(oflags); + ret.push(opts['wal_enabled'] != null ? !!opts['wal_enabled'] : true); + ret.push(opts['wal_check_crc_on_checkpoint']); + ret.push(opts['wal_checkpoint_buffer_sz']); + ret.push(opts['wal_checkpoint_timeout_sec']); + ret.push(opts['wal_savepoint_timeout_sec']); + ret.push(opts['wal_wal_buffer_sz']); + ret.push(opts['document_buffer_sz']); + ret.push(opts['sort_buffer_sz']); + ret.push(opts['http_enabled']); + ret.push(opts['http_access_token']); + ret.push(opts['http_bind']); + ret.push(opts['http_max_body_size']); + ret.push(opts['http_port']); + ret.push(opts['http_read_anon']); + return ret; + } + + const inst = new EJDB2(toArgs()); + return inst._impl.open().then(() => inst); + } + + constructor(args) { + this._impl = new EJDB2Impl(args); + } + + /** + * Closes database instance. + * @return {Promise} + */ + close() { + return this._impl.close(); + } + + /** + * Saves [json] document under specified [id] or create a document + * with new generated `id`. Returns promise holding actual document `id`. + * + * @param {String} collection + * @param {Object|string} json + * @param {number} [id] + * @returns {Promise} + */ + put(collection, json, id) { + if (typeof json !== 'string') { + json = JSON.stringify(json); + } + return this._impl.put(collection, json, id); + } + + /** + * Apply rfc6902/rfc7386 JSON [patch] to the document identified by [id]. + * + * @param {String} collection + * @param {Object|string} json + * @param {number} id + * @return {Promise} + */ + patch(collection, json, id) { + return this._impl.patch(collection, json, id); + } + + /** + * Apply JSON merge patch (rfc7396) to the document identified by `id` or + * insert new document under specified `id`. + * + * @param {String} collection + * @param {Object|string} json + * @param {number} id + * @return {Promise} + */ + patchOrPut(collection, json, id) { + return this._impl.patch_or_put(collection, json, id); + } + + /** + * Get json body of document identified by [id] and stored in [collection]. + * + * @param {String} collection + * @param {number} id + * @return {Promise} JSON object + */ + get(collection, id) { + return this._impl.get(collection, id).then((raw) => JSON.parse(raw)); + } + + /** + * Get json body of document identified by [id] and stored in [collection]. + * If document with given `id` is not found then `null` will be resoved. + * + * @param {string} collection + * @param {number} id + * @return {Promise} JSON object + */ + getOrNull(collection, id) { + return this.get(collection, id).catch((err) => { + if (JBE.isNotFound(err)) { + return null; + } else { + return Promise.reject(err); + } + }); + } + + /** + * Get json body with database metadata. + * + * @return {Promise} + */ + info() { + return this._impl.info().then((raw) => JSON.parse(raw)); + } + + /** + * Removes document idenfied by [id] from [collection]. + * + * @param {String} collection + * @param {number} id + * @return {Promise} + */ + del(collection, id) { + return this._impl.del(collection, id); + } + + /** + * Renames collection. + * + * @param {String} oldCollectionName Collection to be renamed + * @param {String} newCollectionName New name of collection + * @return {Promise} + */ + renameCollection(oldCollectionName, newCollectionName) { + return this._impl.rename_collection(oldCollectionName, newCollectionName); + } + + /** + * Ensures json document database index specified by [path] json pointer to string data type. + * + * @param {String} collection + * @param {String} path + * @param {boolean} [unique=false] + * @return {Promise} + */ + ensureStringIndex(collection, path, unique) { + return this._impl.index(collection, path, 0x04 | (unique ? 0x01 : 0), false); + } + + /** + * Removes specified database index. + * + * @param {String} collection + * @param {String} path + * @param {boolean} [unique=false] + * @return {Promise} + */ + removeStringIndex(collection, path, unique) { + return this._impl.index(collection, path, 0x04 | (unique ? 0x01 : 0), true); + } + + /** + * Ensures json document database index specified by [path] json pointer to integer data type. + * + * @param {String} collection + * @param {String} path + * @param {boolean} [unique=false] + * @return {Promise} + */ + ensureIntIndex(collection, path, unique) { + return this._impl.index(collection, path, 0x08 | (unique ? 0x01 : 0), false); + } + + /** + * Removes specified database index. + * + * @param {String} collection + * @param {String} path + * @param {boolean} [unique=false] + * @return {Promise} + */ + removeIntIndex(collection, path, unique) { + return this._impl.index(collection, path, 0x08 | (unique ? 0x01 : 0), true); + } + + /** + * Ensures json document database index specified by [path] json pointer to floating point data type. + * + * @param {String} collection + * @param {String} path + * @param {boolean} [unique=false] + * @return {Promise} + */ + ensureFloatIndex(collection, path, unique) { + return this._impl.index(collection, path, 0x10 | (unique ? 0x01 : 0), false); + } + + /** + * Removes specified database index. + * + * @param {String} collection + * @param {String} path + * @param {boolean} [unique=false] + * @return {Promise} + */ + removeFloatIndex(collection, path, unique) { + return this._impl.index(collection, path, 0x10 | (unique ? 0x01 : 0), true); + } + + /** + * Removes database [collection]. + * + * @param {String} collection + * @return {Promise} + */ + removeCollection(collection) { + return this._impl.rmcoll(collection); + } + + /** + * Create instance of [query] specified for [collection]. + * If [collection] is not specified a [query] spec must contain collection name, + * eg: `@mycollection/[foo=bar]` + * + * @param {String} query + * @param {String} [collection] + * @returns {JQL} + */ + createQuery(query, collection) { + return new JQL(this, query, collection); + } + + /** + * Creates an online database backup image and copies it into the specified [fileName]. + * During online backup phase read/write database operations are allowed and not + * blocked for significant amount of time. Returns promise with backup + * finish time as number of milliseconds since epoch. + * + * @param {String} fileName Backup image file path. + * @returns {Promise} + */ + onlineBackup(fileName) { + return this._impl.online_backup(fileName); + } +} + +module.exports = { + EJDB2, + JBE +}; + + diff --git a/src/bindings/ejdb2_node/install.js b/src/bindings/ejdb2_node/install.js new file mode 100644 index 0000000..4ad2199 --- /dev/null +++ b/src/bindings/ejdb2_node/install.js @@ -0,0 +1,89 @@ +/************************************************************************************************** + * EJDB2 Node.js native API binding. + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +const { promisify } = require('util'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const rimraf = promisify(require('rimraf')); +const extract = promisify(require('extract-zip')); +const readdir = promisify(fs.readdir); + +const utils = require('./utils'); +const REVISION = require('./package.json')['revision']; + +function hasRevision() { + return REVISION && REVISION.length && REVISION != '@GIT_REVISION@'; +} + +async function install() { + + let out = await utils.runProcessAndGetOutput('cmake', ['--version']).catch(() => { + console.error('Unable to find executable'); + process.exit(1); + }); + console.log(out); + + out = await utils.runProcessAndGetOutput('make', ['--version']).catch(() => { + console.error('Unable to find executable'); + process.exit(1); + }); + console.log(out); + + console.log('Building EJDB2 native binding...'); + const wdir = await promisify(fs.mkdtemp)(path.join(os.tmpdir(), 'ejdb2-node')); + console.log(`Git revision: ${REVISION}`); + console.log(`Build temp dir: ${wdir}`); + + let dist = path.join(wdir, 'dist.zip'); + await utils.download(`https://github.com/Softmotions/ejdb/archive/${REVISION}.zip`, dist); + await extract(dist, { dir: wdir }); + + dist = (await readdir(wdir)).find(fn => fn.startsWith(`ejdb-${REVISION}`)); + if (dist == null) throw Error(`Invalid distrib dir ${wdir}`); + dist = path.join(wdir, dist); + + const buildDir = path.join(dist, 'build'); + fs.mkdirSync(buildDir); + + await utils.runProcess( + 'cmake', + ['..', '-DCMAKE_BUILD_TYPE=Release', '-DBUILD_NODEJS_BINDING=ON', `-DNODE_BIN_ROOT=${__dirname}`], + buildDir); + await utils.runProcess('make', [], buildDir); + await rimraf(wdir); +} + +if (process.platform.toLowerCase().indexOf('win') == 0) { // Windows system + console.error('Building for windows is currently not supported'); + process.exit(1); +} +if (hasRevision() && !fs.existsSync(path.join(utils.binariesDir), 'ejdb2_node.node')) { + install().catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/src/bindings/ejdb2_node/js_native_api.h b/src/bindings/ejdb2_node/js_native_api.h new file mode 100644 index 0000000..bd8bd35 --- /dev/null +++ b/src/bindings/ejdb2_node/js_native_api.h @@ -0,0 +1,557 @@ +#ifndef SRC_JS_NATIVE_API_H_ +#define SRC_JS_NATIVE_API_H_ + +// This file needs to be compatible with C compilers. +#include // NOLINT(modernize-deprecated-headers) +#include // NOLINT(modernize-deprecated-headers) + +// Use INT_MAX, this should only be consumed by the pre-processor anyway. +#define NAPI_VERSION_EXPERIMENTAL 2147483647 +#ifndef NAPI_VERSION +#ifdef NAPI_EXPERIMENTAL +#define NAPI_VERSION NAPI_VERSION_EXPERIMENTAL +#else +// The baseline version for N-API. +// The NAPI_VERSION controls which version will be used by default when +// compilling a native addon. If the addon developer specifically wants to use +// functions available in a new version of N-API that is not yet ported in all +// LTS versions, they can set NAPI_VERSION knowing that they have specifically +// depended on that version. +#define NAPI_VERSION 7 +#endif +#endif + +#include "js_native_api_types.h" + +// If you need __declspec(dllimport), either include instead, or +// define NAPI_EXTERN as __declspec(dllimport) on the compiler's command line. +#ifndef NAPI_EXTERN + #ifdef _WIN32 + #define NAPI_EXTERN __declspec(dllexport) + #elif defined(__wasm32__) + #define NAPI_EXTERN __attribute__((visibility("default"))) \ + __attribute__((__import_module__("napi"))) + #else + #define NAPI_EXTERN __attribute__((visibility("default"))) + #endif +#endif + +#define NAPI_AUTO_LENGTH SIZE_MAX + +#ifdef __cplusplus +#define EXTERN_C_START extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_START +#define EXTERN_C_END +#endif + +EXTERN_C_START + +NAPI_EXTERN napi_status +napi_get_last_error_info(napi_env env, + const napi_extended_error_info** result); + +// Getters for defined singletons +NAPI_EXTERN napi_status napi_get_undefined(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_get_null(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_get_global(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_get_boolean(napi_env env, + bool value, + napi_value* result); + +// Methods to create Primitive types/Objects +NAPI_EXTERN napi_status napi_create_object(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_create_array(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_create_array_with_length(napi_env env, + size_t length, + napi_value* result); +NAPI_EXTERN napi_status napi_create_double(napi_env env, + double value, + napi_value* result); +NAPI_EXTERN napi_status napi_create_int32(napi_env env, + int32_t value, + napi_value* result); +NAPI_EXTERN napi_status napi_create_uint32(napi_env env, + uint32_t value, + napi_value* result); +NAPI_EXTERN napi_status napi_create_int64(napi_env env, + int64_t value, + napi_value* result); +NAPI_EXTERN napi_status napi_create_string_latin1(napi_env env, + const char* str, + size_t length, + napi_value* result); +NAPI_EXTERN napi_status napi_create_string_utf8(napi_env env, + const char* str, + size_t length, + napi_value* result); +NAPI_EXTERN napi_status napi_create_string_utf16(napi_env env, + const char16_t* str, + size_t length, + napi_value* result); +NAPI_EXTERN napi_status napi_create_symbol(napi_env env, + napi_value description, + napi_value* result); +NAPI_EXTERN napi_status napi_create_function(napi_env env, + const char* utf8name, + size_t length, + napi_callback cb, + void* data, + napi_value* result); +NAPI_EXTERN napi_status napi_create_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); +NAPI_EXTERN napi_status napi_create_type_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); +NAPI_EXTERN napi_status napi_create_range_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); + +// Methods to get the native napi_value from Primitive type +NAPI_EXTERN napi_status napi_typeof(napi_env env, + napi_value value, + napi_valuetype* result); +NAPI_EXTERN napi_status napi_get_value_double(napi_env env, + napi_value value, + double* result); +NAPI_EXTERN napi_status napi_get_value_int32(napi_env env, + napi_value value, + int32_t* result); +NAPI_EXTERN napi_status napi_get_value_uint32(napi_env env, + napi_value value, + uint32_t* result); +NAPI_EXTERN napi_status napi_get_value_int64(napi_env env, + napi_value value, + int64_t* result); +NAPI_EXTERN napi_status napi_get_value_bool(napi_env env, + napi_value value, + bool* result); + +// Copies LATIN-1 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status napi_get_value_string_latin1(napi_env env, + napi_value value, + char* buf, + size_t bufsize, + size_t* result); + +// Copies UTF-8 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status napi_get_value_string_utf8(napi_env env, + napi_value value, + char* buf, + size_t bufsize, + size_t* result); + +// Copies UTF-16 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status napi_get_value_string_utf16(napi_env env, + napi_value value, + char16_t* buf, + size_t bufsize, + size_t* result); + +// Methods to coerce values +// These APIs may execute user scripts +NAPI_EXTERN napi_status napi_coerce_to_bool(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status napi_coerce_to_number(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status napi_coerce_to_object(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status napi_coerce_to_string(napi_env env, + napi_value value, + napi_value* result); + +// Methods to work with Objects +NAPI_EXTERN napi_status napi_get_prototype(napi_env env, + napi_value object, + napi_value* result); +NAPI_EXTERN napi_status napi_get_property_names(napi_env env, + napi_value object, + napi_value* result); +NAPI_EXTERN napi_status napi_set_property(napi_env env, + napi_value object, + napi_value key, + napi_value value); +NAPI_EXTERN napi_status napi_has_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status napi_get_property(napi_env env, + napi_value object, + napi_value key, + napi_value* result); +NAPI_EXTERN napi_status napi_delete_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status napi_has_own_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status napi_set_named_property(napi_env env, + napi_value object, + const char* utf8name, + napi_value value); +NAPI_EXTERN napi_status napi_has_named_property(napi_env env, + napi_value object, + const char* utf8name, + bool* result); +NAPI_EXTERN napi_status napi_get_named_property(napi_env env, + napi_value object, + const char* utf8name, + napi_value* result); +NAPI_EXTERN napi_status napi_set_element(napi_env env, + napi_value object, + uint32_t index, + napi_value value); +NAPI_EXTERN napi_status napi_has_element(napi_env env, + napi_value object, + uint32_t index, + bool* result); +NAPI_EXTERN napi_status napi_get_element(napi_env env, + napi_value object, + uint32_t index, + napi_value* result); +NAPI_EXTERN napi_status napi_delete_element(napi_env env, + napi_value object, + uint32_t index, + bool* result); +NAPI_EXTERN napi_status +napi_define_properties(napi_env env, + napi_value object, + size_t property_count, + const napi_property_descriptor* properties); + +// Methods to work with Arrays +NAPI_EXTERN napi_status napi_is_array(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status napi_get_array_length(napi_env env, + napi_value value, + uint32_t* result); + +// Methods to compare values +NAPI_EXTERN napi_status napi_strict_equals(napi_env env, + napi_value lhs, + napi_value rhs, + bool* result); + +// Methods to work with Functions +NAPI_EXTERN napi_status napi_call_function(napi_env env, + napi_value recv, + napi_value func, + size_t argc, + const napi_value* argv, + napi_value* result); +NAPI_EXTERN napi_status napi_new_instance(napi_env env, + napi_value constructor, + size_t argc, + const napi_value* argv, + napi_value* result); +NAPI_EXTERN napi_status napi_instanceof(napi_env env, + napi_value object, + napi_value constructor, + bool* result); + +// Methods to work with napi_callbacks + +// Gets all callback info in a single call. (Ugly, but faster.) +NAPI_EXTERN napi_status napi_get_cb_info( + napi_env env, // [in] NAPI environment handle + napi_callback_info cbinfo, // [in] Opaque callback-info handle + size_t* argc, // [in-out] Specifies the size of the provided argv array + // and receives the actual count of args. + napi_value* argv, // [out] Array of values + napi_value* this_arg, // [out] Receives the JS 'this' arg for the call + void** data); // [out] Receives the data pointer for the callback. + +NAPI_EXTERN napi_status napi_get_new_target(napi_env env, + napi_callback_info cbinfo, + napi_value* result); +NAPI_EXTERN napi_status +napi_define_class(napi_env env, + const char* utf8name, + size_t length, + napi_callback constructor, + void* data, + size_t property_count, + const napi_property_descriptor* properties, + napi_value* result); + +// Methods to work with external data objects +NAPI_EXTERN napi_status napi_wrap(napi_env env, + napi_value js_object, + void* native_object, + napi_finalize finalize_cb, + void* finalize_hint, + napi_ref* result); +NAPI_EXTERN napi_status napi_unwrap(napi_env env, + napi_value js_object, + void** result); +NAPI_EXTERN napi_status napi_remove_wrap(napi_env env, + napi_value js_object, + void** result); +NAPI_EXTERN napi_status napi_create_external(napi_env env, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +NAPI_EXTERN napi_status napi_get_value_external(napi_env env, + napi_value value, + void** result); + +// Methods to control object lifespan + +// Set initial_refcount to 0 for a weak reference, >0 for a strong reference. +NAPI_EXTERN napi_status napi_create_reference(napi_env env, + napi_value value, + uint32_t initial_refcount, + napi_ref* result); + +// Deletes a reference. The referenced value is released, and may +// be GC'd unless there are other references to it. +NAPI_EXTERN napi_status napi_delete_reference(napi_env env, napi_ref ref); + +// Increments the reference count, optionally returning the resulting count. +// After this call the reference will be a strong reference because its +// refcount is >0, and the referenced object is effectively "pinned". +// Calling this when the refcount is 0 and the object is unavailable +// results in an error. +NAPI_EXTERN napi_status napi_reference_ref(napi_env env, + napi_ref ref, + uint32_t* result); + +// Decrements the reference count, optionally returning the resulting count. +// If the result is 0 the reference is now weak and the object may be GC'd +// at any time if there are no other references. Calling this when the +// refcount is already 0 results in an error. +NAPI_EXTERN napi_status napi_reference_unref(napi_env env, + napi_ref ref, + uint32_t* result); + +// Attempts to get a referenced value. If the reference is weak, +// the value might no longer be available, in that case the call +// is still successful but the result is NULL. +NAPI_EXTERN napi_status napi_get_reference_value(napi_env env, + napi_ref ref, + napi_value* result); + +NAPI_EXTERN napi_status napi_open_handle_scope(napi_env env, + napi_handle_scope* result); +NAPI_EXTERN napi_status napi_close_handle_scope(napi_env env, + napi_handle_scope scope); +NAPI_EXTERN napi_status +napi_open_escapable_handle_scope(napi_env env, + napi_escapable_handle_scope* result); +NAPI_EXTERN napi_status +napi_close_escapable_handle_scope(napi_env env, + napi_escapable_handle_scope scope); + +NAPI_EXTERN napi_status napi_escape_handle(napi_env env, + napi_escapable_handle_scope scope, + napi_value escapee, + napi_value* result); + +// Methods to support error handling +NAPI_EXTERN napi_status napi_throw(napi_env env, napi_value error); +NAPI_EXTERN napi_status napi_throw_error(napi_env env, + const char* code, + const char* msg); +NAPI_EXTERN napi_status napi_throw_type_error(napi_env env, + const char* code, + const char* msg); +NAPI_EXTERN napi_status napi_throw_range_error(napi_env env, + const char* code, + const char* msg); +NAPI_EXTERN napi_status napi_is_error(napi_env env, + napi_value value, + bool* result); + +// Methods to support catching exceptions +NAPI_EXTERN napi_status napi_is_exception_pending(napi_env env, bool* result); +NAPI_EXTERN napi_status napi_get_and_clear_last_exception(napi_env env, + napi_value* result); + +// Methods to work with array buffers and typed arrays +NAPI_EXTERN napi_status napi_is_arraybuffer(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status napi_create_arraybuffer(napi_env env, + size_t byte_length, + void** data, + napi_value* result); +NAPI_EXTERN napi_status +napi_create_external_arraybuffer(napi_env env, + void* external_data, + size_t byte_length, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +NAPI_EXTERN napi_status napi_get_arraybuffer_info(napi_env env, + napi_value arraybuffer, + void** data, + size_t* byte_length); +NAPI_EXTERN napi_status napi_is_typedarray(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status napi_create_typedarray(napi_env env, + napi_typedarray_type type, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result); +NAPI_EXTERN napi_status napi_get_typedarray_info(napi_env env, + napi_value typedarray, + napi_typedarray_type* type, + size_t* length, + void** data, + napi_value* arraybuffer, + size_t* byte_offset); + +NAPI_EXTERN napi_status napi_create_dataview(napi_env env, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result); +NAPI_EXTERN napi_status napi_is_dataview(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status napi_get_dataview_info(napi_env env, + napi_value dataview, + size_t* bytelength, + void** data, + napi_value* arraybuffer, + size_t* byte_offset); + +// version management +NAPI_EXTERN napi_status napi_get_version(napi_env env, uint32_t* result); + +// Promises +NAPI_EXTERN napi_status napi_create_promise(napi_env env, + napi_deferred* deferred, + napi_value* promise); +NAPI_EXTERN napi_status napi_resolve_deferred(napi_env env, + napi_deferred deferred, + napi_value resolution); +NAPI_EXTERN napi_status napi_reject_deferred(napi_env env, + napi_deferred deferred, + napi_value rejection); +NAPI_EXTERN napi_status napi_is_promise(napi_env env, + napi_value value, + bool* is_promise); + +// Running a script +NAPI_EXTERN napi_status napi_run_script(napi_env env, + napi_value script, + napi_value* result); + +// Memory management +NAPI_EXTERN napi_status napi_adjust_external_memory(napi_env env, + int64_t change_in_bytes, + int64_t* adjusted_value); + +#if NAPI_VERSION >= 5 + +// Dates +NAPI_EXTERN napi_status napi_create_date(napi_env env, + double time, + napi_value* result); + +NAPI_EXTERN napi_status napi_is_date(napi_env env, + napi_value value, + bool* is_date); + +NAPI_EXTERN napi_status napi_get_date_value(napi_env env, + napi_value value, + double* result); + +// Add finalizer for pointer +NAPI_EXTERN napi_status napi_add_finalizer(napi_env env, + napi_value js_object, + void* native_object, + napi_finalize finalize_cb, + void* finalize_hint, + napi_ref* result); + +#endif // NAPI_VERSION >= 5 + +#if NAPI_VERSION >= 6 + +// BigInt +NAPI_EXTERN napi_status napi_create_bigint_int64(napi_env env, + int64_t value, + napi_value* result); +NAPI_EXTERN napi_status napi_create_bigint_uint64(napi_env env, + uint64_t value, + napi_value* result); +NAPI_EXTERN napi_status napi_create_bigint_words(napi_env env, + int sign_bit, + size_t word_count, + const uint64_t* words, + napi_value* result); +NAPI_EXTERN napi_status napi_get_value_bigint_int64(napi_env env, + napi_value value, + int64_t* result, + bool* lossless); +NAPI_EXTERN napi_status napi_get_value_bigint_uint64(napi_env env, + napi_value value, + uint64_t* result, + bool* lossless); +NAPI_EXTERN napi_status napi_get_value_bigint_words(napi_env env, + napi_value value, + int* sign_bit, + size_t* word_count, + uint64_t* words); + +// Object +NAPI_EXTERN napi_status +napi_get_all_property_names(napi_env env, + napi_value object, + napi_key_collection_mode key_mode, + napi_key_filter key_filter, + napi_key_conversion key_conversion, + napi_value* result); + +// Instance data +NAPI_EXTERN napi_status napi_set_instance_data(napi_env env, + void* data, + napi_finalize finalize_cb, + void* finalize_hint); + +NAPI_EXTERN napi_status napi_get_instance_data(napi_env env, + void** data); +#endif // NAPI_VERSION >= 6 + +#if NAPI_VERSION >= 7 +// ArrayBuffer detaching +NAPI_EXTERN napi_status napi_detach_arraybuffer(napi_env env, + napi_value arraybuffer); + +NAPI_EXTERN napi_status napi_is_detached_arraybuffer(napi_env env, + napi_value value, + bool* result); +#endif // NAPI_VERSION >= 7 + +#ifdef NAPI_EXPERIMENTAL +// Type tagging +NAPI_EXTERN napi_status napi_type_tag_object(napi_env env, + napi_value value, + const napi_type_tag* type_tag); + +NAPI_EXTERN napi_status +napi_check_object_type_tag(napi_env env, + napi_value value, + const napi_type_tag* type_tag, + bool* result); +#endif // NAPI_EXPERIMENTAL + +EXTERN_C_END + +#endif // SRC_JS_NATIVE_API_H_ diff --git a/src/bindings/ejdb2_node/js_native_api_types.h b/src/bindings/ejdb2_node/js_native_api_types.h new file mode 100644 index 0000000..f139a1e --- /dev/null +++ b/src/bindings/ejdb2_node/js_native_api_types.h @@ -0,0 +1,146 @@ +#ifndef SRC_JS_NATIVE_API_TYPES_H_ +#define SRC_JS_NATIVE_API_TYPES_H_ + +// This file needs to be compatible with C compilers. +// This is a public include file, and these includes have essentially +// became part of it's API. +#include // NOLINT(modernize-deprecated-headers) +#include // NOLINT(modernize-deprecated-headers) + +#if !defined __cplusplus || (defined(_MSC_VER) && _MSC_VER < 1900) + typedef uint16_t char16_t; +#endif + +// JSVM API types are all opaque pointers for ABI stability +// typedef undefined structs instead of void* for compile time type safety +typedef struct napi_env__* napi_env; +typedef struct napi_value__* napi_value; +typedef struct napi_ref__* napi_ref; +typedef struct napi_handle_scope__* napi_handle_scope; +typedef struct napi_escapable_handle_scope__* napi_escapable_handle_scope; +typedef struct napi_callback_info__* napi_callback_info; +typedef struct napi_deferred__* napi_deferred; + +typedef enum { + napi_default = 0, + napi_writable = 1 << 0, + napi_enumerable = 1 << 1, + napi_configurable = 1 << 2, + + // Used with napi_define_class to distinguish static properties + // from instance properties. Ignored by napi_define_properties. + napi_static = 1 << 10, +} napi_property_attributes; + +typedef enum { + // ES6 types (corresponds to typeof) + napi_undefined, + napi_null, + napi_boolean, + napi_number, + napi_string, + napi_symbol, + napi_object, + napi_function, + napi_external, + napi_bigint, +} napi_valuetype; + +typedef enum { + napi_int8_array, + napi_uint8_array, + napi_uint8_clamped_array, + napi_int16_array, + napi_uint16_array, + napi_int32_array, + napi_uint32_array, + napi_float32_array, + napi_float64_array, + napi_bigint64_array, + napi_biguint64_array, +} napi_typedarray_type; + +typedef enum { + napi_ok, + napi_invalid_arg, + napi_object_expected, + napi_string_expected, + napi_name_expected, + napi_function_expected, + napi_number_expected, + napi_boolean_expected, + napi_array_expected, + napi_generic_failure, + napi_pending_exception, + napi_cancelled, + napi_escape_called_twice, + napi_handle_scope_mismatch, + napi_callback_scope_mismatch, + napi_queue_full, + napi_closing, + napi_bigint_expected, + napi_date_expected, + napi_arraybuffer_expected, + napi_detachable_arraybuffer_expected, +} napi_status; +// Note: when adding a new enum value to `napi_status`, please also update +// `const int last_status` in `napi_get_last_error_info()' definition, +// in file js_native_api_v8.cc. Please also update the definition of +// `napi_status` in doc/api/n-api.md to reflect the newly added value(s). + +typedef napi_value (*napi_callback)(napi_env env, + napi_callback_info info); +typedef void (*napi_finalize)(napi_env env, + void* finalize_data, + void* finalize_hint); + +typedef struct { + // One of utf8name or name should be NULL. + const char* utf8name; + napi_value name; + + napi_callback method; + napi_callback getter; + napi_callback setter; + napi_value value; + + napi_property_attributes attributes; + void* data; +} napi_property_descriptor; + +typedef struct { + const char* error_message; + void* engine_reserved; + uint32_t engine_error_code; + napi_status error_code; +} napi_extended_error_info; + +#if NAPI_VERSION >= 6 +typedef enum { + napi_key_include_prototypes, + napi_key_own_only +} napi_key_collection_mode; + +typedef enum { + napi_key_all_properties = 0, + napi_key_writable = 1, + napi_key_enumerable = 1 << 1, + napi_key_configurable = 1 << 2, + napi_key_skip_strings = 1 << 3, + napi_key_skip_symbols = 1 << 4 +} napi_key_filter; + +typedef enum { + napi_key_keep_numbers, + napi_key_numbers_to_strings +} napi_key_conversion; +#endif // NAPI_VERSION >= 6 + +#ifdef NAPI_EXPERIMENTAL +typedef struct { + uint64_t lower; + uint64_t upper; +} napi_type_tag; +#endif // NAPI_EXPERIMENTAL + +#endif // SRC_JS_NATIVE_API_TYPES_H_ diff --git a/src/bindings/ejdb2_node/node_api.h b/src/bindings/ejdb2_node/node_api.h new file mode 100644 index 0000000..577a1dc --- /dev/null +++ b/src/bindings/ejdb2_node/node_api.h @@ -0,0 +1,268 @@ +#ifndef SRC_NODE_API_H_ +#define SRC_NODE_API_H_ + +#ifdef BUILDING_NODE_EXTENSION + #ifdef _WIN32 + // Building native module against node + #define NAPI_EXTERN __declspec(dllimport) + #elif defined(__wasm32__) + #define NAPI_EXTERN __attribute__((__import_module__("napi"))) + #endif +#endif +#include "js_native_api.h" +#include "node_api_types.h" + +struct uv_loop_s; // Forward declaration. + +#ifdef _WIN32 +# define NAPI_MODULE_EXPORT __declspec(dllexport) +#else +# define NAPI_MODULE_EXPORT __attribute__((visibility("default"))) +#endif + +#if defined(__GNUC__) +# define NAPI_NO_RETURN __attribute__((noreturn)) +#elif defined(_WIN32) +# define NAPI_NO_RETURN __declspec(noreturn) +#else +# define NAPI_NO_RETURN +#endif + +typedef napi_value (*napi_addon_register_func)(napi_env env, + napi_value exports); + +typedef struct { + int nm_version; + unsigned int nm_flags; + const char* nm_filename; + napi_addon_register_func nm_register_func; + const char* nm_modname; + void* nm_priv; + void* reserved[4]; +} napi_module; + +#define NAPI_MODULE_VERSION 1 + +#if defined(_MSC_VER) +#pragma section(".CRT$XCU", read) +#define NAPI_C_CTOR(fn) \ + static void __cdecl fn(void); \ + __declspec(dllexport, allocate(".CRT$XCU")) void(__cdecl * fn##_)(void) = \ + fn; \ + static void __cdecl fn(void) +#else +#define NAPI_C_CTOR(fn) \ + static void fn(void) __attribute__((constructor)); \ + static void fn(void) +#endif + +#define NAPI_MODULE_X(modname, regfunc, priv, flags) \ + EXTERN_C_START \ + static napi_module _module = \ + { \ + NAPI_MODULE_VERSION, \ + flags, \ + __FILE__, \ + regfunc, \ + #modname, \ + priv, \ + {0}, \ + }; \ + NAPI_C_CTOR(_register_ ## modname) { \ + napi_module_register(&_module); \ + } \ + EXTERN_C_END + +#define NAPI_MODULE_INITIALIZER_X(base, version) \ + NAPI_MODULE_INITIALIZER_X_HELPER(base, version) +#define NAPI_MODULE_INITIALIZER_X_HELPER(base, version) base##version + +#ifdef __wasm32__ +#define NAPI_WASM_INITIALIZER \ + NAPI_MODULE_INITIALIZER_X(napi_register_wasm_v, NAPI_MODULE_VERSION) +#define NAPI_MODULE(modname, regfunc) \ + EXTERN_C_START \ + NAPI_MODULE_EXPORT napi_value NAPI_WASM_INITIALIZER(napi_env env, \ + napi_value exports) { \ + return regfunc(env, exports); \ + } \ + EXTERN_C_END +#else +#define NAPI_MODULE(modname, regfunc) \ + NAPI_MODULE_X(modname, regfunc, NULL, 0) // NOLINT (readability/null_usage) +#endif + +#define NAPI_MODULE_INITIALIZER_BASE napi_register_module_v + +#define NAPI_MODULE_INITIALIZER \ + NAPI_MODULE_INITIALIZER_X(NAPI_MODULE_INITIALIZER_BASE, \ + NAPI_MODULE_VERSION) + +#define NAPI_MODULE_INIT() \ + EXTERN_C_START \ + NAPI_MODULE_EXPORT napi_value \ + NAPI_MODULE_INITIALIZER(napi_env env, napi_value exports); \ + EXTERN_C_END \ + NAPI_MODULE(NODE_GYP_MODULE_NAME, NAPI_MODULE_INITIALIZER) \ + napi_value NAPI_MODULE_INITIALIZER(napi_env env, \ + napi_value exports) + +EXTERN_C_START + +NAPI_EXTERN void napi_module_register(napi_module* mod); + +NAPI_EXTERN NAPI_NO_RETURN void napi_fatal_error(const char* location, + size_t location_len, + const char* message, + size_t message_len); + +// Methods for custom handling of async operations +NAPI_EXTERN napi_status napi_async_init(napi_env env, + napi_value async_resource, + napi_value async_resource_name, + napi_async_context* result); + +NAPI_EXTERN napi_status napi_async_destroy(napi_env env, + napi_async_context async_context); + +NAPI_EXTERN napi_status napi_make_callback(napi_env env, + napi_async_context async_context, + napi_value recv, + napi_value func, + size_t argc, + const napi_value* argv, + napi_value* result); + +// Methods to provide node::Buffer functionality with napi types +NAPI_EXTERN napi_status napi_create_buffer(napi_env env, + size_t length, + void** data, + napi_value* result); +NAPI_EXTERN napi_status napi_create_external_buffer(napi_env env, + size_t length, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +NAPI_EXTERN napi_status napi_create_buffer_copy(napi_env env, + size_t length, + const void* data, + void** result_data, + napi_value* result); +NAPI_EXTERN napi_status napi_is_buffer(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status napi_get_buffer_info(napi_env env, + napi_value value, + void** data, + size_t* length); + +// Methods to manage simple async operations +NAPI_EXTERN +napi_status napi_create_async_work(napi_env env, + napi_value async_resource, + napi_value async_resource_name, + napi_async_execute_callback execute, + napi_async_complete_callback complete, + void* data, + napi_async_work* result); +NAPI_EXTERN napi_status napi_delete_async_work(napi_env env, + napi_async_work work); +NAPI_EXTERN napi_status napi_queue_async_work(napi_env env, + napi_async_work work); +NAPI_EXTERN napi_status napi_cancel_async_work(napi_env env, + napi_async_work work); + +// version management +NAPI_EXTERN +napi_status napi_get_node_version(napi_env env, + const napi_node_version** version); + +#if NAPI_VERSION >= 2 + +// Return the current libuv event loop for a given environment +NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env, + struct uv_loop_s** loop); + +#endif // NAPI_VERSION >= 2 + +#if NAPI_VERSION >= 3 + +NAPI_EXTERN napi_status napi_fatal_exception(napi_env env, napi_value err); + +NAPI_EXTERN napi_status napi_add_env_cleanup_hook(napi_env env, + void (*fun)(void* arg), + void* arg); + +NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(napi_env env, + void (*fun)(void* arg), + void* arg); + +NAPI_EXTERN napi_status napi_open_callback_scope(napi_env env, + napi_value resource_object, + napi_async_context context, + napi_callback_scope* result); + +NAPI_EXTERN napi_status napi_close_callback_scope(napi_env env, + napi_callback_scope scope); + +#endif // NAPI_VERSION >= 3 + +#if NAPI_VERSION >= 4 + +#ifndef __wasm32__ +// Calling into JS from other threads +NAPI_EXTERN napi_status +napi_create_threadsafe_function(napi_env env, + napi_value func, + napi_value async_resource, + napi_value async_resource_name, + size_t max_queue_size, + size_t initial_thread_count, + void* thread_finalize_data, + napi_finalize thread_finalize_cb, + void* context, + napi_threadsafe_function_call_js call_js_cb, + napi_threadsafe_function* result); + +NAPI_EXTERN napi_status +napi_get_threadsafe_function_context(napi_threadsafe_function func, + void** result); + +NAPI_EXTERN napi_status +napi_call_threadsafe_function(napi_threadsafe_function func, + void* data, + napi_threadsafe_function_call_mode is_blocking); + +NAPI_EXTERN napi_status +napi_acquire_threadsafe_function(napi_threadsafe_function func); + +NAPI_EXTERN napi_status +napi_release_threadsafe_function(napi_threadsafe_function func, + napi_threadsafe_function_release_mode mode); + +NAPI_EXTERN napi_status +napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func); + +NAPI_EXTERN napi_status +napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func); +#endif // __wasm32__ + +#endif // NAPI_VERSION >= 4 + +#ifdef NAPI_EXPERIMENTAL + +NAPI_EXTERN napi_status napi_add_async_cleanup_hook( + napi_env env, + napi_async_cleanup_hook hook, + void* arg, + napi_async_cleanup_hook_handle* remove_handle); + +NAPI_EXTERN napi_status napi_remove_async_cleanup_hook( + napi_async_cleanup_hook_handle remove_handle); + +#endif // NAPI_EXPERIMENTAL + +EXTERN_C_END + +#endif // SRC_NODE_API_H_ diff --git a/src/bindings/ejdb2_node/node_api_types.h b/src/bindings/ejdb2_node/node_api_types.h new file mode 100644 index 0000000..0e400e9 --- /dev/null +++ b/src/bindings/ejdb2_node/node_api_types.h @@ -0,0 +1,50 @@ +#ifndef SRC_NODE_API_TYPES_H_ +#define SRC_NODE_API_TYPES_H_ + +#include "js_native_api_types.h" + +typedef struct napi_callback_scope__* napi_callback_scope; +typedef struct napi_async_context__* napi_async_context; +typedef struct napi_async_work__* napi_async_work; +#if NAPI_VERSION >= 4 +typedef struct napi_threadsafe_function__* napi_threadsafe_function; +#endif // NAPI_VERSION >= 4 + +#if NAPI_VERSION >= 4 +typedef enum { + napi_tsfn_release, + napi_tsfn_abort +} napi_threadsafe_function_release_mode; + +typedef enum { + napi_tsfn_nonblocking, + napi_tsfn_blocking +} napi_threadsafe_function_call_mode; +#endif // NAPI_VERSION >= 4 + +typedef void (*napi_async_execute_callback)(napi_env env, + void* data); +typedef void (*napi_async_complete_callback)(napi_env env, + napi_status status, + void* data); +#if NAPI_VERSION >= 4 +typedef void (*napi_threadsafe_function_call_js)(napi_env env, + napi_value js_callback, + void* context, + void* data); +#endif // NAPI_VERSION >= 4 + +typedef struct { + uint32_t major; + uint32_t minor; + uint32_t patch; + const char* release; +} napi_node_version; + +#ifdef NAPI_EXPERIMENTAL +typedef struct napi_async_cleanup_hook_handle__* napi_async_cleanup_hook_handle; +typedef void (*napi_async_cleanup_hook)(napi_async_cleanup_hook_handle handle, + void* data); +#endif // NAPI_EXPERIMENTAL + +#endif // SRC_NODE_API_TYPES_H_ diff --git a/src/bindings/ejdb2_node/package.json b/src/bindings/ejdb2_node/package.json new file mode 100644 index 0000000..cf25032 --- /dev/null +++ b/src/bindings/ejdb2_node/package.json @@ -0,0 +1,70 @@ +{ + "name": "ejdb2_node", + "version": "@EJDB2_NODE_VERSION@", + "repository": "https://github.com/Softmotions/ejdb.git", + "author": "Anton Adamansky ", + "description": "EJDB2 Node.js native binding", + "license": "MIT", + "main": "index.js", + "types": "index.d.ts", + "engines": { + "node": ">=10.0.0", + "npm": ">=6.0.0" + }, + "os": [ + "darwin", + "linux", + "!win32" + ], + "cpu": [ + "x64", + "x32", + "arm", + "arm64" + ], + "scripts": { + "test": "node install.js && ava -s", + "install": "node install.js" + }, + "files": [ + "*.js", + "index.d.ts", + "Changelog", + "README.md" + ], + "revision": "@GIT_REVISION@", + "dependencies": { + "extract-zip": "^1.6.7", + "gauge": "^2.7.4", + "request": "^2.88.0", + "request-progress": "^3.0.0", + "rimraf": "^2.6.3", + "semver": "^6.2.0" + }, + "devDependencies": { + "@types/node": "^12.0.10", + "ava": "^2.2.0", + "chai": "^4.2.0" + }, + "resolutions": { + "lodash.merge": "4.6.2" + }, + "keywords": [ + "ejdb", + "ejdb2", + "nosql", + "json", + "database", + "storage", + "embedded", + "embeddable", + "native", + "binding" + ], + "ava": { + "files": [ + "test.js" + ], + "failWithoutAssertions": false + } +} diff --git a/src/bindings/ejdb2_node/test.js b/src/bindings/ejdb2_node/test.js new file mode 100644 index 0000000..2e1b949 --- /dev/null +++ b/src/bindings/ejdb2_node/test.js @@ -0,0 +1,248 @@ +/************************************************************************************************** + * EJDB2 Node.js native API binding. + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +const test = require('ava'); +const { assert } = require('chai'); +const { EJDB2, JBE } = require('./index'); + +test('Main', async (t) => { + const db = await EJDB2.open('hello.db', { truncate: true }); + + let q = db.createQuery('@mycoll/*'); + t.true(q != null); + t.is(q.collection, 'mycoll'); + t.true(q.db != null); + + let id = await db.put('mycoll', { 'foo': 'bar' }); + t.is(id, 1); + t.throwsAsync(db.put('mycoll', '{"'), { + code: '@ejdb IWRC:86005 put', + message: 'Unquoted JSON string (JBL_ERROR_PARSE_UNQUOTED_STRING)' + }); + + let doc = await db.get('mycoll', 1); + t.deepEqual(doc, { foo: 'bar' }); + + await db.put('mycoll', { 'foo': 'baz' }); + + const list = await db.createQuery('@mycoll/*').list({ limit: 1 }); + t.is(list.length, 1); + + let first = await db.createQuery('@mycoll/*').first(); + t.true(first != null); + t.deepEqual(first.json, { foo: 'baz' }); + t.is(first._raw, null); + + first = await db.createQuery('@mycoll/[zzz=bbb]').first(); + t.is(first, undefined); + + let firstN = await db.createQuery('@mycoll/*').firstN(5); + t.true(firstN != null && firstN.length == 2); + t.deepEqual(firstN[0].json, { foo: 'baz' }); + t.deepEqual(firstN[1].json, { foo: 'bar' }); + + firstN = await db.createQuery('@mycoll/*').firstN(1); + t.true(firstN != null && firstN.length == 1); + t.deepEqual(firstN[0].json, { foo: 'baz' }); + + // Query 1 + const rbuf = []; + for await (const doc of q.stream()) { + rbuf.push(doc.id); + rbuf.push(doc._raw); + } + t.is(rbuf.toString(), '2,{"foo":"baz"},1,{"foo":"bar"}'); + + // Query 2 + rbuf.length = 0; + for await (const doc of db.createQuery('@mycoll/[foo=zaz]').stream()) { + rbuf.push(doc.id); + rbuf.push(doc._raw); + } + t.is(rbuf.length, 0); + + // Query 3 + for await (const doc of db.createQuery('/[foo=bar]', 'mycoll').stream()) { + rbuf.push(doc.id); + rbuf.push(doc._raw); + } + t.is(rbuf.toString(), '1,{"foo":"bar"}'); + + let error = null; + try { + await db.createQuery('@mycoll/[').stream(); + } catch (e) { + error = e; + t.true(JBE.isInvalidQuery(e)); + } + t.true(error != null); + error = null; + + let count = await db.createQuery('@mycoll/* | count').scalarInt(); + t.is(count, 2); + + // Del + await db.del('mycoll', 1); + + error = null; + try { + await db.get('mycoll', 1); + } catch (e) { + error = e; + t.true(JBE.isNotFound(e)); + } + t.true(error != null); + error = null; + + count = await db.createQuery('@mycoll/* | count').scalarInt(); + t.is(count, 1); + + // Patch + await db.patch('mycoll', '[{"op":"add", "path":"/baz", "value":"qux"}]', 2); + doc = await db.get('mycoll', 2); + t.deepEqual(doc, { foo: 'baz', baz: 'qux' }); + + // DB Info + doc = await db.info(); + assert.deepNestedInclude(doc, { + 'collections[0]': { + name: 'mycoll', + rnum: 1, + dbid: 3, + indexes: [] + } + }); + + // Indexes + await db.ensureStringIndex('mycoll', '/foo', true); + doc = await db.info(); + assert.deepNestedInclude(doc, { + 'collections[0].indexes[0]': { + ptr: '/foo', + mode: 5, + idbf: 0, + dbid: 4, + rnum: 1 + } + }); + + // Test JQL set + doc = await db.createQuery('@mycoll/[foo=:?]').setString(0, 'baz').first(); + assert.deepInclude(doc.json, { + foo: 'baz', + baz: 'qux' + }); + + let log = null; + // Test explain log + await db.createQuery('@mycoll/[foo=:?]').setString(0, 'baz').completionPromise({ + explainCallback: (l) => { + log = l; + } + }); + assert.isString(log); + t.true(log.indexOf('[INDEX] MATCHED UNIQUE|STR|1 /foo EXPR1: \'foo = :?\' INIT: IWKV_CURSOR_EQ') != -1); + + + doc = await db.createQuery('@mycoll/[foo=:?] and /[baz=:?]') + .setString(0, 'baz') + .setString(1, 'qux') + .first(); + assert.deepInclude(doc.json, { + foo: 'baz', + baz: 'qux' + }); + + doc = await db.createQuery('@mycoll/[foo=:foo] and /[baz=:baz]') + .setString('foo', 'baz') + .setString('baz', 'qux') + .first(); + assert.deepInclude(doc.json, { + foo: 'baz', + baz: 'qux' + }); + + doc = await db.createQuery('@mycoll/[foo re :?]').setRegexp(0, '.*').first(); + assert.deepInclude(doc.json, { + foo: 'baz', + baz: 'qux' + }); + + doc = await db.createQuery('@mycoll/* | /foo').first(); + assert.deepEqual(doc.json, { foo: 'baz' }); + + await db.removeStringIndex('mycoll', '/foo', true); + doc = await db.info(); + assert.deepNestedInclude(doc, { + 'collections[0].indexes': [] + }); + + await db.removeCollection('mycoll'); + doc = await db.info(); + assert.deepNestedInclude(doc, { + 'collections': [] + }); + + q = db.createQuery('@c1/* | limit 2'); + t.is(q.limit, 2); + + q = db.createQuery('@c2/*'); + t.is(q.limit, 0); + + // Rename collection + id = await db.put('cc1', { 'foo': 1 }); + doc = await db.get('cc1', id); + t.deepEqual(doc, { 'foo': 1 }); + + await db.renameCollection('cc1', 'cc2'); + doc = await db.get('cc2', id); + t.deepEqual(doc, { 'foo': 1 }); + + const ts0 = +new Date(); + const ts = await db.onlineBackup('hello-bkp.db'); + t.true(ts0 < ts); + + await db.close(); + + // Restore from backup + const db2 = await EJDB2.open('hello-bkp.db', { truncate: false }); + doc = await db2.get('cc2', id); + t.deepEqual(doc, { 'foo': 1 }); + await db2.close(); + + //global.gc(); +}); + +// test('GC', async () => { +// global.gc(); +// await new Promise((resolve) => { +// global.gc(); +// setTimeout(() => { +// resolve(); +// }, 500); +// }); +// }); + diff --git a/src/bindings/ejdb2_node/tsconfig.json b/src/bindings/ejdb2_node/tsconfig.json new file mode 100644 index 0000000..5031bf9 --- /dev/null +++ b/src/bindings/ejdb2_node/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es6", + "moduleResolution": "node", + "module": "commonjs", + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "declaration": false, + "pretty": true, + "lib": [ + "es6", + "esnext.asynciterable" + ], + "baseUrl": "." + }, + "exclude": [ + "node_modules/**", + "**/*.aot.ts" + ] +} \ No newline at end of file diff --git a/src/bindings/ejdb2_node/tslint.json b/src/bindings/ejdb2_node/tslint.json new file mode 100644 index 0000000..62318cc --- /dev/null +++ b/src/bindings/ejdb2_node/tslint.json @@ -0,0 +1,53 @@ +{ + "extends": "tslint:recommended", + "rules": { + "no-var-requires": false, + "ordered-imports": [ + false + ], + "quotemark": [ + "single" + ], + "no-console": [ + false + ], + "no-unused-expression": [ + "allow-fast-null-checks", + "allow-new", + "allow-tagged-template" + ], + "only-arrow-functions": [ + false + ], + "no-conditional-assignment": false, + "no-angle-bracket-type-assertion": false, + "whitespace": [ + "check-branch", + "check-decl", + "check-module", + "check-operator", + "check-preblock", + "check-separator", + "check-type", + "check-typecast" + ], + "member-access": [ + "check-constructor", + "no-public" + ], + "object-literal-sort-keys": [ + false + ], + "max-classes-per-file": [ + false + ], + "no-trailing-whitespace": false, + "arrow-parens": false, + "no-empty": false, + "no-shadowed-variable": true, + "no-string-literal": false, + "trailing-comma": [ + false + ] + } +} \ No newline at end of file diff --git a/src/bindings/ejdb2_node/utils.js b/src/bindings/ejdb2_node/utils.js new file mode 100644 index 0000000..d7637ba --- /dev/null +++ b/src/bindings/ejdb2_node/utils.js @@ -0,0 +1,128 @@ +/************************************************************************************************** + * EJDB2 Node.js native API binding. + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +const fs = require('fs'); +const request = require('request'); +const progress = require('request-progress'); +const Gauge = require('gauge'); +const childProcess = require('child_process'); + +let platform = process.platform.toLowerCase(); +if (platform.indexOf('win') === 0) { + platform = 'windows'; +} + +/** + * @param {String} name + * @param {Array} [args] + * @param {String} [cwd] + * @return {Promise} + */ +function runProcess(name, args, cwd) { + args = args || []; + console.log(`Spawn: ${name} ${args}`); + return awaitProcess(childProcess.spawn(name, args, { + stdio: ['ignore', process.stdout, process.stderr], + cwd + })); +} + +function runProcessAndGetOutput(name, args, cwd) { + const res = []; + args = args || []; + console.log(`Spawn: ${name} ${args}`); + const proc = childProcess.spawn(name, args, { + stdio: ['ignore', 'pipe', 'pipe'], + cwd + }); + proc.stdout.on('data', (data) => res.push(data)); + proc.stderr.on('data', (data) => res.push(data)); + return awaitProcess(proc).then(() => { + return res.join(); + }); +} + +/** + * @return {Promise} + */ +function awaitProcess(childProcess) { + return new Promise((resolve, reject) => { + childProcess.once('exit', (code) => { + if (code === 0) { + resolve(undefined); + } else { + reject(new Error('Exit with error code: ' + code)); + } + }); + childProcess.once('error', (err) => reject(err)); + }); +} + +/** + * @param {String} url + * @param {String} dest + * @return {Promise} + */ +function download(url, dest, name) { + const gauge = new Gauge(); + return new Promise((resolve, reject) => { + let completed = false; + function handleError(err) { + if (!completed) { + gauge.hide(); + reject(err); + completed = true; + } + } + progress(request(url), { + throttle: 500, + delay: 0 + }) + .on('progress', (state) => gauge.show(name || url, state.percent)) + .on('error', (err) => handleError(err)) + .on('end', () => { + if (!completed) { + gauge.hide(); + resolve(dest); + completed = true; + } + }) + .on('response', function (response) { + if (response.statusCode >= 400) { + handleError(`HTTP ${response.statusCode}: ${url}`); + } + }) + .pipe(fs.createWriteStream(dest)); + }); +} + +module.exports = { + binariesDir: `${platform}-${process.arch}`, + download, + awaitProcess, + runProcess, + runProcessAndGetOutput +}; \ No newline at end of file diff --git a/src/bindings/ejdb2_node/version.txt b/src/bindings/ejdb2_node/version.txt new file mode 100644 index 0000000..d8263ee --- /dev/null +++ b/src/bindings/ejdb2_node/version.txt @@ -0,0 +1 @@ +2 \ No newline at end of file diff --git a/src/bindings/ejdb2_node/yarn.lock b/src/bindings/ejdb2_node/yarn.lock new file mode 100644 index 0000000..5174d90 --- /dev/null +++ b/src/bindings/ejdb2_node/yarn.lock @@ -0,0 +1,3212 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ava/babel-plugin-throws-helper@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@ava/babel-plugin-throws-helper/-/babel-plugin-throws-helper-3.0.0.tgz#2c933ec22da0c4ce1fc5369f2b95452c70420586" + integrity sha512-mN9UolOs4WX09QkheU1ELkVy2WPnwonlO3XMdN8JF8fQqRVgVTR21xDbvEOUsbwz6Zwjq7ji9yzyjuXqDPalxg== + +"@ava/babel-preset-stage-4@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@ava/babel-preset-stage-4/-/babel-preset-stage-4-3.0.0.tgz#32c46b22b640d1ba0c6e38ae4abd58efab965558" + integrity sha512-uI5UBx++UsckkfnbF0HH6jvTIvM4r/Kxt1ROO2YXKu5H15sScAtxUIAHiUVbPIw24zPqz/PlF3xxlIDuyFzlQw== + dependencies: + "@babel/plugin-proposal-async-generator-functions" "^7.2.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" + "@babel/plugin-transform-dotall-regex" "^7.4.3" + "@babel/plugin-transform-modules-commonjs" "^7.4.3" + +"@ava/babel-preset-transform-test-files@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@ava/babel-preset-transform-test-files/-/babel-preset-transform-test-files-5.0.0.tgz#e06fc762069511e597531cc1120e22216aac6981" + integrity sha512-rqgyQwkT0+j2JzYP51dOv80u33rzAvjBtXRzUON+7+6u26mjoudRXci2+1s18rat8r4uOlZfbzm114YS6pwmYw== + dependencies: + "@ava/babel-plugin-throws-helper" "^3.0.0" + babel-plugin-espower "^3.0.1" + +"@babel/code-frame@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" + integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/core@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.5.0.tgz#6ed6a2881ad48a732c5433096d96d1b0ee5eb734" + integrity sha512-6Isr4X98pwXqHvtigw71CKgmhL1etZjPs5A67jL/w0TkLM9eqmFR40YrnJvEc1WnMZFsskjsmid8bHZyxKEAnw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.5.0" + "@babel/helpers" "^7.5.0" + "@babel/parser" "^7.5.0" + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.5.0" + "@babel/types" "^7.5.0" + convert-source-map "^1.1.0" + debug "^4.1.0" + json5 "^2.1.0" + lodash "^4.17.11" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.0.0", "@babel/generator@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.5.0.tgz#f20e4b7a91750ee8b63656073d843d2a736dca4a" + integrity sha512-1TTVrt7J9rcG5PMjvO7VEG3FrEoEJNHxumRq66GemPmzboLWtIjjcJgk8rokuAS7IiRSpgVSu5Vb9lc99iJkOA== + dependencies: + "@babel/types" "^7.5.0" + jsesc "^2.5.1" + lodash "^4.17.11" + source-map "^0.5.0" + trim-right "^1.0.1" + +"@babel/helper-annotate-as-pure@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" + integrity sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-function-name@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" + integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== + dependencies: + "@babel/helper-get-function-arity" "^7.0.0" + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-get-function-arity@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" + integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-module-imports@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d" + integrity sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-module-transforms@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz#96115ea42a2f139e619e98ed46df6019b94414b8" + integrity sha512-3Z1yp8TVQf+B4ynN7WoHPKS8EkdTbgAEy0nU0rs/1Kw4pDgmvYH3rz3aI11KgxKCba2cn7N+tqzV1mY2HMN96w== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-simple-access" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.4.4" + "@babel/template" "^7.4.4" + "@babel/types" "^7.4.4" + lodash "^4.17.11" + +"@babel/helper-plugin-utils@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" + integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== + +"@babel/helper-regex@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.4.4.tgz#a47e02bc91fb259d2e6727c2a30013e3ac13c4a2" + integrity sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q== + dependencies: + lodash "^4.17.11" + +"@babel/helper-remap-async-to-generator@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz#361d80821b6f38da75bd3f0785ece20a88c5fe7f" + integrity sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-wrap-function" "^7.1.0" + "@babel/template" "^7.1.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-simple-access@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz#65eeb954c8c245beaa4e859da6188f39d71e585c" + integrity sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w== + dependencies: + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-split-export-declaration@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" + integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q== + dependencies: + "@babel/types" "^7.4.4" + +"@babel/helper-wrap-function@^7.1.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz#c4e0012445769e2815b55296ead43a958549f6fa" + integrity sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/template" "^7.1.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.2.0" + +"@babel/helpers@^7.5.0": + version "7.5.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.5.2.tgz#97424dc82fc0041f4c751119b4d2b1ec68cdb5ba" + integrity sha512-NDkkTqDvgFUeo8djXBOiwO/mFjownznOWvmP9hvNdfiFUmx0nwNOqxuaTTbxjH744eQsD9M5ubC7gdANBvIWPw== + dependencies: + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.5.0" + "@babel/types" "^7.5.0" + +"@babel/highlight@^7.0.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" + integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.0.0", "@babel/parser@^7.4.4", "@babel/parser@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.0.tgz#3e0713dff89ad6ae37faec3b29dcfc5c979770b7" + integrity sha512-I5nW8AhGpOXGCCNYGc+p7ExQIBxRFnS2fd/d862bNOKvmoEPjYPcfIjsfdy0ujagYOIYPczKgD9l3FsgTkAzKA== + +"@babel/plugin-proposal-async-generator-functions@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e" + integrity sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-remap-async-to-generator" "^7.1.0" + "@babel/plugin-syntax-async-generators" "^7.2.0" + +"@babel/plugin-proposal-optional-catch-binding@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz#135d81edb68a081e55e56ec48541ece8065c38f5" + integrity sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" + +"@babel/plugin-syntax-async-generators@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz#69e1f0db34c6f5a0cf7e2b3323bf159a76c8cb7f" + integrity sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-object-rest-spread@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e" + integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz#a94013d6eda8908dfe6a477e7f9eda85656ecf5c" + integrity sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-dotall-regex@^7.4.3": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz#361a148bc951444312c69446d76ed1ea8e4450c3" + integrity sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.4.4" + regexpu-core "^4.5.4" + +"@babel/plugin-transform-modules-commonjs@^7.4.3": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.5.0.tgz#425127e6045231360858eeaa47a71d75eded7a74" + integrity sha512-xmHq0B+ytyrWJvQTc5OWAC4ii6Dhr0s22STOoydokG51JjWhyYo5mRPXoi+ZmtHQhZZwuXNN+GG5jy5UZZJxIQ== + dependencies: + "@babel/helper-module-transforms" "^7.4.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-simple-access" "^7.1.0" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/template@^7.1.0", "@babel/template@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" + integrity sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.4.4" + "@babel/types" "^7.4.4" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.5.0.tgz#4216d6586854ef5c3c4592dab56ec7eb78485485" + integrity sha512-SnA9aLbyOCcnnbQEGwdfBggnc142h/rbqqsXcaATj2hZcegCl903pUD/lfpsNBlBSuWow/YDfRyJuWi2EPR5cg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.5.0" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.4.4" + "@babel/parser" "^7.5.0" + "@babel/types" "^7.5.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.11" + +"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.4.4", "@babel/types@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.0.tgz#e47d43840c2e7f9105bc4d3a2c371b4d0c7832ab" + integrity sha512-UFpDVqRABKsW01bvw7/wSUe56uy6RXM5+VJibVVAybDGxEW25jdwiFJEf7ASvSaC7sN7rbE/l3cLp2izav+CtQ== + dependencies: + esutils "^2.0.2" + lodash "^4.17.11" + to-fast-properties "^2.0.0" + +"@concordance/react@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@concordance/react/-/react-2.0.0.tgz#aef913f27474c53731f4fd79cc2f54897de90fde" + integrity sha512-huLSkUuM2/P+U0uy2WwlKuixMsTODD8p4JVQBI4VKeopkiN0C7M3N9XYVawb4M+4spN5RrO/eLhk7KoQX6nsfA== + dependencies: + arrify "^1.0.1" + +"@nodelib/fs.scandir@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.1.tgz#7fa8fed654939e1a39753d286b48b4836d00e0eb" + integrity sha512-NT/skIZjgotDSiXs0WqYhgcuBKhUMgfekCmCGtkUAiLqZdOnrdjmZr9wRl3ll64J9NF79uZ4fk16Dx0yMc/Xbg== + dependencies: + "@nodelib/fs.stat" "2.0.1" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.1", "@nodelib/fs.stat@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.1.tgz#814f71b1167390cfcb6a6b3d9cdeb0951a192c14" + integrity sha512-+RqhBlLn6YRBGOIoVYthsG0J9dfpO79eJyN7BYBkZJtfqrBwf2KK+rD/M/yjZR6WBmIhAgOV7S60eCgaSWtbFw== + +"@nodelib/fs.walk@^1.2.1": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.2.tgz#6a6450c5e17012abd81450eb74949a4d970d2807" + integrity sha512-J/DR3+W12uCzAJkw7niXDcqcKBg6+5G5Q/ZpThpGNzAUz70eOR6RV4XnnSN01qHZiVl0eavoxJsBypQoKsV2QQ== + dependencies: + "@nodelib/fs.scandir" "2.1.1" + fastq "^1.6.0" + +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + +"@types/events@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== + +"@types/glob@^7.1.1": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" + integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== + dependencies: + "@types/events" "*" + "@types/minimatch" "*" + "@types/node" "*" + +"@types/minimatch@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + +"@types/node@*": + version "12.6.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.1.tgz#d5544f6de0aae03eefbb63d5120f6c8be0691946" + integrity sha512-rp7La3m845mSESCgsJePNL/JQyhkOJA6G4vcwvVgkDAwHhGdq5GCumxmPjEk1MZf+8p5ZQAUE7tqgQRQTXN7uQ== + +"@types/node@^12.0.10": + version "12.0.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.10.tgz#51babf9c7deadd5343620055fc8aff7995c8b031" + integrity sha512-LcsGbPomWsad6wmMNv7nBLw7YYYyfdYcz6xryKYQhx89c3XXan+8Q6AJ43G5XDIaklaVkK3mE4fCb0SBvMiPSQ== + +ajv@^6.5.5: + version "6.10.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" + integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-align@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" + integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== + dependencies: + string-width "^3.0.0" + +ansi-escapes@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.2.0.tgz#c38600259cefba178ee3f7166c5ea3a5dd2e88fc" + integrity sha512-0+VX4uhi8m3aNbzoqKmkAVOEj6uQzcUHXoFPkKjhZPTpGRUBqVh930KbB6PS4zIyDZccphlLIYlu8nsjFzkXwg== + dependencies: + type-fest "^0.5.2" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.0.0.tgz#f6b84e8fc97ea7add7a53b7530ef28f3fde0e048" + integrity sha512-8zjUtFJ3db/QoPXuuEMloS2AUf79/yeyttJ7Abr3hteopJu9HK8vsgGviGUMq+zyA6cZZO6gAyZoMTF6TgaEjA== + dependencies: + color-convert "^2.0.0" + +anymatch@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.0.3.tgz#2fb624fe0e84bccab00afee3d0006ed310f22f09" + integrity sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= + dependencies: + array-uniq "^1.0.1" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= + +array-uniq@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-2.1.0.tgz#46603d5e28e79bfd02b046fcc1d77c6820bd8e98" + integrity sha512-bdHxtev7FN6+MXI1YFW0Q8mQ8dTJc2S8AMfju+ZR77pbg2yAdVyDlwkaUI7Har0LyOMRFPHrJ9lYdyjZZswdlQ== + +arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= + +arrify@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" + integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +ava@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ava/-/ava-2.2.0.tgz#5a7d799c2c8cc5926f95d2b76bdfe6081fe9a4f8" + integrity sha512-lROj3eQ8L4ZjfiN5P8UGekEQLfKmseGMtat6pUOHgJLEb2K1kT0ZpR/IlWwuytjvwO6nZpzpD+QsTW/XiayIgg== + dependencies: + "@ava/babel-preset-stage-4" "^3.0.0" + "@ava/babel-preset-transform-test-files" "^5.0.0" + "@babel/core" "^7.5.0" + "@babel/generator" "^7.5.0" + "@babel/plugin-syntax-async-generators" "^7.2.0" + "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" + "@concordance/react" "^2.0.0" + ansi-escapes "^4.2.0" + ansi-styles "^4.0.0" + arr-flatten "^1.1.0" + array-union "^2.1.0" + array-uniq "^2.1.0" + arrify "^2.0.1" + bluebird "^3.5.5" + chalk "^2.4.2" + chokidar "^3.0.2" + chunkd "^1.0.0" + ci-parallel-vars "^1.0.0" + clean-stack "^2.1.0" + clean-yaml-object "^0.1.0" + cli-cursor "^3.1.0" + cli-truncate "^2.0.0" + code-excerpt "^2.1.1" + common-path-prefix "^1.0.0" + concordance "^4.0.0" + convert-source-map "^1.6.0" + currently-unhandled "^0.4.1" + debug "^4.1.1" + del "^4.1.1" + dot-prop "^5.1.0" + emittery "^0.4.1" + empower-core "^1.2.0" + equal-length "^1.0.0" + escape-string-regexp "^2.0.0" + esm "^3.2.25" + figures "^3.0.0" + find-up "^4.1.0" + get-port "^5.0.0" + globby "^10.0.1" + ignore-by-default "^1.0.0" + import-local "^3.0.1" + indent-string "^4.0.0" + is-ci "^2.0.0" + is-error "^2.2.2" + is-observable "^2.0.0" + is-plain-object "^3.0.0" + is-promise "^2.1.0" + lodash "^4.17.11" + loud-rejection "^2.1.0" + make-dir "^3.0.0" + matcher "^2.0.0" + md5-hex "^3.0.0" + meow "^5.0.0" + micromatch "^4.0.2" + ms "^2.1.2" + observable-to-promise "^1.0.0" + ora "^3.4.0" + package-hash "^4.0.0" + pkg-conf "^3.1.0" + plur "^3.1.1" + pretty-ms "^5.0.0" + require-precompiled "^0.1.0" + resolve-cwd "^3.0.0" + slash "^3.0.0" + source-map-support "^0.5.12" + stack-utils "^1.0.2" + strip-ansi "^5.2.0" + strip-bom-buf "^2.0.0" + supertap "^1.0.0" + supports-color "^7.0.0" + trim-off-newlines "^1.0.1" + trim-right "^1.0.1" + unique-temp-dir "^1.0.0" + update-notifier "^3.0.1" + write-file-atomic "^3.0.0" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + +babel-plugin-dynamic-import-node@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" + integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== + dependencies: + object.assign "^4.1.0" + +babel-plugin-espower@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-espower/-/babel-plugin-espower-3.0.1.tgz#180db17126f88e754105b8b5216d21e520a6bd4e" + integrity sha512-Ms49U7VIAtQ/TtcqRbD6UBmJBUCSxiC3+zPc+eGqxKUIFO1lTshyEDRUjhoAbd2rWfwYf3cZ62oXozrd8W6J0A== + dependencies: + "@babel/generator" "^7.0.0" + "@babel/parser" "^7.0.0" + call-matcher "^1.0.0" + core-js "^2.0.0" + espower-location-detector "^1.0.0" + espurify "^1.6.0" + estraverse "^4.1.1" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +binary-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" + integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== + +bluebird@^3.5.5: + version "3.5.5" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" + integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== + +boxen@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-3.2.0.tgz#fbdff0de93636ab4450886b6ff45b92d098f45eb" + integrity sha512-cU4J/+NodM3IHdSL2yN8bqYqnmlBTidDR4RC7nJs61ZmtGz8VZzM3HLQX0zY5mrSmPtR3xWwsq2jOUQqFZN8+A== + dependencies: + ansi-align "^3.0.0" + camelcase "^5.3.1" + chalk "^2.4.2" + cli-boxes "^2.2.0" + string-width "^3.0.0" + term-size "^1.2.0" + type-fest "^0.3.0" + widest-line "^2.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.1, braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + +call-matcher@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/call-matcher/-/call-matcher-1.1.0.tgz#23b2c1bc7a8394c8be28609d77ddbd5786680432" + integrity sha512-IoQLeNwwf9KTNbtSA7aEBb1yfDbdnzwjCetjkC8io5oGeOmK2CBNdg0xr+tadRYKO0p7uQyZzvon0kXlZbvGrw== + dependencies: + core-js "^2.0.0" + deep-equal "^1.0.0" + espurify "^1.6.0" + estraverse "^4.0.0" + +call-signature@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/call-signature/-/call-signature-0.0.2.tgz#a84abc825a55ef4cb2b028bd74e205a65b9a4996" + integrity sha1-qEq8glpV70yysCi9dOIFpluaSZY= + +camelcase-keys@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77" + integrity sha1-oqpfsa9oh1glnDLBQUJteJI7m3c= + dependencies: + camelcase "^4.1.0" + map-obj "^2.0.0" + quick-lru "^1.0.0" + +camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chai@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" + integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.2" + deep-eql "^3.0.1" + get-func-name "^2.0.0" + pathval "^1.1.0" + type-detect "^4.0.5" + +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +check-error@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= + +chokidar@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.0.2.tgz#0d1cd6d04eb2df0327446188cd13736a3367d681" + integrity sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA== + dependencies: + anymatch "^3.0.1" + braces "^3.0.2" + glob-parent "^5.0.0" + is-binary-path "^2.1.0" + is-glob "^4.0.1" + normalize-path "^3.0.0" + readdirp "^3.1.1" + optionalDependencies: + fsevents "^2.0.6" + +chunkd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/chunkd/-/chunkd-1.0.0.tgz#4ead4a3704bcce510c4bb4d4a8be30c557836dd1" + integrity sha512-xx3Pb5VF9QaqCotolyZ1ywFBgyuJmu6+9dLiqBxgelEse9Xsr3yUlpoX3O4Oh11M00GT2kYMsRByTKIMJW2Lkg== + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +ci-parallel-vars@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ci-parallel-vars/-/ci-parallel-vars-1.0.0.tgz#af97729ed1c7381911ca37bcea263d62638701b3" + integrity sha512-u6dx20FBXm+apMi+5x7UVm6EH7BL1gc4XrcnQewjcB7HWRcor/V5qWc3RG2HwpgDJ26gIi2DSEu3B7sXynAw/g== + +clean-stack@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.1.0.tgz#9e7fec7f3f8340a2ab4f127c80273085e8fbbdd0" + integrity sha512-uQWrpRm+iZZUCAp7ZZJQbd4Za9I3AjR/3YTjmcnAtkauaIm/T5CT6U8zVI6e60T6OANqBFAzuR9/HB3NzuZCRA== + +clean-yaml-object@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz#63fb110dc2ce1a84dc21f6d9334876d010ae8b68" + integrity sha1-Y/sRDcLOGoTcIfbZM0h20BCui2g= + +cli-boxes@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" + integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w== + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + dependencies: + restore-cursor "^2.0.0" + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-spinners@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.2.0.tgz#e8b988d9206c692302d8ee834e7a85c0144d8f77" + integrity sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ== + +cli-truncate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.0.0.tgz#68ff6aaa53b203b52ad89b8b1a80f1f61ad1e1d5" + integrity sha512-C4hp+8GCIFVsUUiXcw+ce+7wexVWImw8rQrgMBFsqerx9LvvcGlwm6sMjQYAEmV/Xb87xc1b5Ttx505MSpZVqg== + dependencies: + slice-ansi "^2.1.0" + string-width "^4.1.0" + +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + +code-excerpt@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/code-excerpt/-/code-excerpt-2.1.1.tgz#5fe3057bfbb71a5f300f659ef2cc0a47651ba77c" + integrity sha512-tJLhH3EpFm/1x7heIW0hemXJTUU5EWl2V0EIX558jp05Mt1U6DVryCgkp3l37cxqs+DNbNgxG43SkwJXpQ14Jw== + dependencies: + convert-to-spaces "^1.0.1" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.0.tgz#9851ac61cc0d3898a8a3088650d5bf447bf69d97" + integrity sha512-hzTicsCJIHdxih9+2aLR1tNGZX5qSJGRHDPVwSY26tVrEf55XNajLOBWz2UuWSIergszA09/bqnOiHyqx9fxQg== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +common-path-prefix@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-1.0.0.tgz#cd52f6f0712e0baab97d6f9732874f22f47752c0" + integrity sha1-zVL28HEuC6q5fW+XModPIvR3UsA= + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +concordance@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/concordance/-/concordance-4.0.0.tgz#5932fdee397d129bdbc3a1885fbe69839b1b7e15" + integrity sha512-l0RFuB8RLfCS0Pt2Id39/oCPykE01pyxgAFypWTlaGRgvLkZrtczZ8atEHpTeEIW+zYWXTBuA9cCSeEOScxReQ== + dependencies: + date-time "^2.1.0" + esutils "^2.0.2" + fast-diff "^1.1.2" + js-string-escape "^1.0.1" + lodash.clonedeep "^4.5.0" + lodash.flattendeep "^4.4.0" + lodash.islength "^4.0.1" + lodash.merge "^4.6.1" + md5-hex "^2.0.0" + semver "^5.5.1" + well-known-symbols "^2.0.0" + +configstore@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-4.0.0.tgz#5933311e95d3687efb592c528b922d9262d227e7" + integrity sha512-CmquAXFBocrzaSM8mtGPMM/HiWmyIpr4CcJl/rgY2uCObZ/S7cKU0silxslqJejl+t/T9HS8E0PUNQD81JGUEQ== + dependencies: + dot-prop "^4.1.0" + graceful-fs "^4.1.2" + make-dir "^1.0.0" + unique-string "^1.0.0" + write-file-atomic "^2.0.0" + xdg-basedir "^3.0.0" + +console-control-strings@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +convert-source-map@^1.1.0, convert-source-map@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" + integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== + dependencies: + safe-buffer "~5.1.1" + +convert-to-spaces@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/convert-to-spaces/-/convert-to-spaces-1.0.2.tgz#7e3e48bbe6d997b1417ddca2868204b4d3d85715" + integrity sha1-fj5Iu+bZl7FBfdyihoIEtNPYVxU= + +core-js@^2.0.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" + integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +crypto-random-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" + integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= + +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= + dependencies: + array-find-index "^1.0.1" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +date-time@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/date-time/-/date-time-2.1.0.tgz#0286d1b4c769633b3ca13e1e62558d2dbdc2eba2" + integrity sha512-/9+C44X7lot0IeiyfgJmETtRMhBidBYM2QFFIkGa0U1k+hSyY87Nw7PY3eDqpvCBm7I3WCSfPeZskW/YYq6m4g== + dependencies: + time-zone "^1.0.0" + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4.1.0, debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +decamelize-keys@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" + integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + dependencies: + mimic-response "^1.0.0" + +deep-eql@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== + dependencies: + type-detect "^4.0.0" + +deep-equal@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + dependencies: + clone "^1.0.2" + +defer-to-connect@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.0.2.tgz#4bae758a314b034ae33902b5aac25a8dd6a8633e" + integrity sha512-k09hcQcTDY+cwgiwa6PYKLm3jlagNzQ+RSvhjzESOGOx+MNOuXkxTfEvPrO1IOQ81tArCFYQgi631clB70RpQw== + +define-properties@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +del@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" + integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== + dependencies: + "@types/glob" "^7.1.1" + globby "^6.1.0" + is-path-cwd "^2.0.0" + is-path-in-cwd "^2.0.0" + p-map "^2.0.0" + pify "^4.0.1" + rimraf "^2.6.3" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dot-prop@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" + integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ== + dependencies: + is-obj "^1.0.0" + +dot-prop@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.1.0.tgz#bdd8c986a77b83e3fca524e53786df916cabbd8a" + integrity sha512-n1oC6NBF+KM9oVXtjmen4Yo7HyAVWV2UUl50dCYJdw2924K6dX9bf9TTTWaKtYlRn0FEtxG27KS80ayVLixxJA== + dependencies: + is-obj "^2.0.0" + +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +emittery@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.4.1.tgz#abe9d3297389ba424ac87e53d1c701962ce7433d" + integrity sha512-r4eRSeStEGf6M5SKdrQhhLK5bOwOBxQhIE3YSTnZE3GpKiLfnnhE+tPtrJE79+eDJgm39BM6LSoI8SCx4HbwlQ== + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +empower-core@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/empower-core/-/empower-core-1.2.0.tgz#ce3fb2484d5187fa29c23fba8344b0b2fdf5601c" + integrity sha512-g6+K6Geyc1o6FdXs9HwrXleCFan7d66G5xSCfSF7x1mJDCes6t0om9lFQG3zOrzh3Bkb/45N0cZ5Gqsf7YrzGQ== + dependencies: + call-signature "0.0.2" + core-js "^2.0.0" + +end-of-stream@^1.1.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" + integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== + dependencies: + once "^1.4.0" + +equal-length@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/equal-length/-/equal-length-1.0.1.tgz#21ca112d48ab24b4e1e7ffc0e5339d31fdfc274c" + integrity sha1-IcoRLUirJLTh5//A5TOdMf38J0w= + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es6-error@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +esm@^3.2.25: + version "3.2.25" + resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" + integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== + +espower-location-detector@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/espower-location-detector/-/espower-location-detector-1.0.0.tgz#a17b7ecc59d30e179e2bef73fb4137704cb331b5" + integrity sha1-oXt+zFnTDheeK+9z+0E3cEyzMbU= + dependencies: + is-url "^1.2.1" + path-is-absolute "^1.0.0" + source-map "^0.5.0" + xtend "^4.0.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +espurify@^1.6.0: + version "1.8.1" + resolved "https://registry.yarnpkg.com/espurify/-/espurify-1.8.1.tgz#5746c6c1ab42d302de10bd1d5bf7f0e8c0515056" + integrity sha512-ZDko6eY/o+D/gHCWyHTU85mKDgYcS4FJj7S+YD6WIInm7GQ6AnOjmcL4+buFV/JOztVLELi/7MmuGU5NHta0Mg== + dependencies: + core-js "^2.0.0" + +estraverse@^4.0.0, estraverse@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= + +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extract-zip@^1.6.7: + version "1.6.7" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" + integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= + dependencies: + concat-stream "1.6.2" + debug "2.6.9" + mkdirp "0.5.1" + yauzl "2.4.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-glob@^3.0.3: + version "3.0.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.0.4.tgz#d484a41005cb6faeb399b951fd1bd70ddaebb602" + integrity sha512-wkIbV6qg37xTJwqSsdnIphL1e+LaGz4AIQqr00mIubMaEhv1/HEmJ0uuCGZRNRUkZZmOB5mJKO0ZUTVq+SxMQg== + dependencies: + "@nodelib/fs.stat" "^2.0.1" + "@nodelib/fs.walk" "^1.2.1" + glob-parent "^5.0.0" + is-glob "^4.0.1" + merge2 "^1.2.3" + micromatch "^4.0.2" + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +fastq@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2" + integrity sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA== + dependencies: + reusify "^1.0.0" + +fd-slicer@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" + integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU= + dependencies: + pend "~1.2.0" + +figures@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.0.0.tgz#756275c964646163cc6f9197c7a0295dbfd04de9" + integrity sha512-HKri+WoWoUgr83pehn/SIgLOMZ9nAWC6dcGj26RY2R4F50u4+RTUz0RCrUlOV3nKRAICW1UGzyb+kcX2qK1S/g== + dependencies: + escape-string-regexp "^1.0.5" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^2.0.6: + version "2.0.7" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a" + integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gauge@^2.7.4: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= + +get-port@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.0.0.tgz#aa22b6b86fd926dd7884de3e23332c9f70c031a6" + integrity sha512-imzMU0FjsZqNa6BqOjbbW6w5BivHIuQKopjpPqcnx0AVHJQKCxK1O+Ab3OrVXhrekqfVMjwA9ZYu062R+KcIsQ== + dependencies: + type-fest "^0.3.0" + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= + +get-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" + integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== + dependencies: + pump "^3.0.0" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob-parent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954" + integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg== + dependencies: + is-glob "^4.0.1" + +glob@^7.0.3, glob@^7.1.3: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-dirs@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" + integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= + dependencies: + ini "^1.3.4" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globby@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.1.tgz#4782c34cb75dd683351335c5829cc3420e606b22" + integrity sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A== + dependencies: + "@types/glob" "^7.1.1" + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.0.3" + glob "^7.1.3" + ignore "^5.1.1" + merge2 "^1.2.3" + slash "^3.0.0" + +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b" + integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg== + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== + +hasha@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.0.0.tgz#fdc3785caea03df29535fc8adb512c3d3a709004" + integrity sha512-PqWdhnQhq6tqD32hZv+l1e5mJHNSudjnaAzgAHfkGiU0ABN6lmbZF8abJIulQHbZ7oiHhP8yL6O910ICMc+5pw== + dependencies: + is-stream "^1.1.0" + type-fest "^0.3.0" + +hosted-git-info@^2.1.4: + version "2.7.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" + integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== + +http-cache-semantics@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz#495704773277eeef6e43f9ab2c2c7d259dda25c5" + integrity sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew== + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +ignore-by-default@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= + +ignore@^5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.2.tgz#e28e584d43ad7e92f96995019cc43b9e1ac49558" + integrity sha512-vdqWBp7MyzdmHkkRWV5nY+PfGRbYbahfuvsBCh277tq+w9zyNi7h5CYJCK0kmzti9kU+O/cB7sE8HvKv6aXAKQ== + +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= + +import-local@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.1.tgz#0b0f61a207b6c6b005c931dc72951ddde8303713" + integrity sha512-XlabwTJ9tPOdyjnGdGvxUMnUhmhlnJhdYjp5e8UDb2fO+5Gto1Frlg66ixVAf1Os+zQlrKt3QlTCVodyxYBZjQ== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +indent-string@^3.0.0, indent-string@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" + integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@^1.3.4, ini@~1.3.0: + version "1.3.7" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" + integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== + +irregular-plurals@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-2.0.0.tgz#39d40f05b00f656d0b7fa471230dd3b714af2872" + integrity sha512-Y75zBYLkh0lJ9qxeHlMjQ7bSbyiSqNW/UOPWDmzC7cXskL1hekSITh1Oc6JV0XCWWZ9DE8VYSB71xocLk3gmGw== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-binary-path@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-error@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/is-error/-/is-error-2.2.2.tgz#c10ade187b3c93510c5470a5567833ee25649843" + integrity sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-installed-globally@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" + integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= + dependencies: + global-dirs "^0.1.0" + is-path-inside "^1.0.0" + +is-npm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-3.0.0.tgz#ec9147bfb629c43f494cf67936a961edec7e8053" + integrity sha512-wsigDr1Kkschp2opC4G3yA6r9EgVA6NjRpWzIi9axXqeIaAATPRJc4uLujXe3Nd9uO8KoDyA4MD6aZSeXTADhA== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-observable@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-2.0.0.tgz#327af1e8cdea9cd717f95911b87c5d34301721a6" + integrity sha512-fhBZv3eFKUbyHXZ1oHujdo2tZ+CNbdpdzzlENgCGZUC8keoGxUew2jYFLYcUB4qo7LDD03o4KK11m/QYD7kEjg== + +is-path-cwd@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-in-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" + integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== + dependencies: + is-path-inside "^2.1.0" + +is-path-inside@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" + integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= + dependencies: + path-is-inside "^1.0.1" + +is-path-inside@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" + integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== + dependencies: + path-is-inside "^1.0.2" + +is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= + +is-plain-object@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.0.tgz#47bfc5da1b5d50d64110806c199359482e75a928" + integrity sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg== + dependencies: + isobject "^4.0.0" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +is-typedarray@^1.0.0, is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-url@^1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" + integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== + +is-utf8@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= + +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" + integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +js-string-escape@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" + integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8= + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.10.0: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json5@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850" + integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ== + dependencies: + minimist "^1.2.0" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + +latest-version@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== + dependencies: + package-json "^6.3.0" + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +load-json-file@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-5.3.0.tgz#4d3c1e01fa1c03ea78a60ac7af932c9ce53403f3" + integrity sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw== + dependencies: + graceful-fs "^4.1.15" + parse-json "^4.0.0" + pify "^4.0.1" + strip-bom "^3.0.0" + type-fest "^0.3.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= + +lodash.islength@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.islength/-/lodash.islength-4.0.1.tgz#4e9868d452575d750affd358c979543dc20ed577" + integrity sha1-Tpho1FJXXXUK/9NYyXlUPcIO1Xc= + +lodash.merge@4.6.2, lodash.merge@^4.6.1: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash@^4.17.11: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + +log-symbols@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" + integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== + dependencies: + chalk "^2.0.1" + +loud-rejection@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + +loud-rejection@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-2.1.0.tgz#4020547ddbc39ed711c8434326df9fc7d2395355" + integrity sha512-g/6MQxUXYHeVqZ4PGpPL1fS1fOvlXoi7bay0pizmjAd/3JhyXwxzwrnr74yzdmhuerlslbRJ3x7IOXzFz0cE5w== + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.2" + +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + +lru-cache@^4.0.1: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +make-dir@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" + integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== + dependencies: + pify "^3.0.0" + +make-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.0.tgz#1b5f39f6b9270ed33f9f054c5c0f84304989f801" + integrity sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw== + dependencies: + semver "^6.0.0" + +map-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= + +map-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9" + integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk= + +matcher@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/matcher/-/matcher-2.0.0.tgz#85fe38d97670dbd2a46590cf099401e2ffb4755c" + integrity sha512-nlmfSlgHBFx36j/Pl/KQPbIaqE8Zf0TqmSMjsuddHDg6PMSVgmyW9HpkLs0o0M1n2GIZ/S2BZBLIww/xjhiGng== + dependencies: + escape-string-regexp "^2.0.0" + +md5-hex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-2.0.0.tgz#d0588e9f1c74954492ecd24ac0ac6ce997d92e33" + integrity sha1-0FiOnxx0lUSS7NJKwKxs6ZfZLjM= + dependencies: + md5-o-matic "^0.1.1" + +md5-hex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-3.0.0.tgz#96cf5c62cedea41e04124b9473ef7481db6de5fb" + integrity sha512-uA+EX5IV1r5lKBJecwTSec3k6xl4ziBUZihRiOpOHCeHjKA0ai6+eImamXQy/cI3Qep5mQgFTeJld9tcwdBNFw== + dependencies: + md5-o-matic "^0.1.1" + +md5-o-matic@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/md5-o-matic/-/md5-o-matic-0.1.1.tgz#822bccd65e117c514fab176b25945d54100a03c3" + integrity sha1-givM1l4RfFFPqxdrJZRdVBAKA8M= + +meow@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-5.0.0.tgz#dfc73d63a9afc714a5e371760eb5c88b91078aa4" + integrity sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig== + dependencies: + camelcase-keys "^4.0.0" + decamelize-keys "^1.0.0" + loud-rejection "^1.0.0" + minimist-options "^3.0.1" + normalize-package-data "^2.3.4" + read-pkg-up "^3.0.0" + redent "^2.0.0" + trim-newlines "^2.0.0" + yargs-parser "^10.0.0" + +merge2@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5" + integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA== + +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +mime-db@1.40.0: + version "1.40.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" + integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.24" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" + integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== + dependencies: + mime-db "1.40.0" + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist-options@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954" + integrity sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ== + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +mkdirp@0.5.1, mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@^2.1.1, ms@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-url@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.3.0.tgz#9c49e10fc1876aeb76dba88bf1b2b5d9fa57b2ee" + integrity sha512-0NLtR71o4k6GLP+mr6Ty34c5GA6CMoEsncKJxvQd8NzPxaHRJNnb5gZE8R1XF4CPIS7QPHLJ74IFszwtNVAHVQ== + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^4.0.1, object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-keys@^1.0.11, object-keys@^1.0.12: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +observable-to-promise@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/observable-to-promise/-/observable-to-promise-1.0.0.tgz#37e136f16a15385ac063411ada0e1202bfff58f4" + integrity sha512-cqnGUrNsE6vdVDTPAX9/WeVzwy/z37vdxupdQXU8vgTXRFH72KCZiZga8aca2ulRPIeem8W3vW9rQHBwfIl2WA== + dependencies: + is-observable "^2.0.0" + symbol-observable "^1.0.4" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + dependencies: + mimic-fn "^1.0.0" + +onetime@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" + integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== + dependencies: + mimic-fn "^2.1.0" + +ora@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" + integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg== + dependencies: + chalk "^2.4.2" + cli-cursor "^2.1.0" + cli-spinners "^2.0.0" + log-symbols "^2.2.0" + strip-ansi "^5.2.0" + wcwidth "^1.0.1" + +os-tmpdir@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0, p-limit@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" + integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== + dependencies: + p-try "^2.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-map@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-hash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" + integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ== + dependencies: + graceful-fs "^4.1.15" + hasha "^5.0.0" + lodash.flattendeep "^4.4.0" + release-zalgo "^1.0.0" + +package-json@^6.3.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.4.0.tgz#4f626976604f4a9a41723ce1792b204a60b1b61e" + integrity sha512-bd1T8OBG7hcvMd9c/udgv6u5v9wISP3Oyl9Cm7Weop8EFwrtcQDnS2sb6zhwqus2WslSr5wSTIPiTTpxxmPm7Q== + dependencies: + got "^9.6.0" + registry-auth-token "^3.4.0" + registry-url "^5.0.0" + semver "^6.1.1" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-ms@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d" + integrity sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA== + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-is-inside@^1.0.1, path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= + +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== + dependencies: + pify "^3.0.0" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pathval@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" + integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +picomatch@^2.0.4, picomatch@^2.0.5: + version "2.0.7" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" + integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA== + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + +pkg-conf@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-3.1.0.tgz#d9f9c75ea1bae0e77938cde045b276dac7cc69ae" + integrity sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ== + dependencies: + find-up "^3.0.0" + load-json-file "^5.2.0" + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +plur@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/plur/-/plur-3.1.1.tgz#60267967866a8d811504fe58f2faaba237546a5b" + integrity sha512-t1Ax8KUvV3FFII8ltczPn2tJdjqbd1sIzu6t4JL7nQ3EyeL/lTrj5PWKb06ic5/6XYDr65rQ4uzQEGN70/6X5w== + dependencies: + irregular-plurals "^2.0.0" + +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= + +pretty-ms@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-5.0.0.tgz#6133a8f55804b208e4728f6aa7bf01085e951e24" + integrity sha512-94VRYjL9k33RzfKiGokPBPpsmloBYSf5Ri+Pq19zlsEcUKFob+admeXr5eFDRuPjFmEOcjJvPGdillYOJyvZ7Q== + dependencies: + parse-ms "^2.1.0" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= + +psl@^1.1.24: + version "1.2.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.2.0.tgz#df12b5b1b3a30f51c329eacbdef98f3a6e136dc6" + integrity sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +quick-lru@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" + integrity sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g= + +rc@^1.1.6, rc@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +read-pkg-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" + integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc= + dependencies: + find-up "^2.0.0" + read-pkg "^3.0.0" + +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + +readable-stream@^2.2.2: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readdirp@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.1.1.tgz#b158123ac343c8b0f31d65680269cc0fc1025db1" + integrity sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ== + dependencies: + picomatch "^2.0.4" + +redent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa" + integrity sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo= + dependencies: + indent-string "^3.0.0" + strip-indent "^2.0.0" + +regenerate-unicode-properties@^8.0.2: + version "8.1.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" + integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== + +regexpu-core@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.5.4.tgz#080d9d02289aa87fe1667a4f5136bc98a6aebaae" + integrity sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.0.2" + regjsgen "^0.5.0" + regjsparser "^0.6.0" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.1.0" + +registry-auth-token@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e" + integrity sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A== + dependencies: + rc "^1.1.6" + safe-buffer "^5.0.1" + +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== + dependencies: + rc "^1.2.8" + +regjsgen@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.0.tgz#a7634dc08f89209c2049adda3525711fb97265dd" + integrity sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA== + +regjsparser@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.0.tgz#f1e6ae8b7da2bae96c99399b868cd6c933a2ba9c" + integrity sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ== + dependencies: + jsesc "~0.5.0" + +release-zalgo@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" + integrity sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA= + dependencies: + es6-error "^4.0.1" + +request-progress@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" + integrity sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4= + dependencies: + throttleit "^1.0.0" + +request@^2.88.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.0" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-precompiled@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/require-precompiled/-/require-precompiled-0.1.0.tgz#5a1b52eb70ebed43eb982e974c85ab59571e56fa" + integrity sha1-WhtS63Dr7UPrmC6XTIWrWVceVvo= + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve@^1.10.0, resolve@^1.3.2: + version "1.11.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e" + integrity sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw== + dependencies: + path-parse "^1.0.6" + +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + dependencies: + lowercase-keys "^1.0.0" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +reusify@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" + integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== + +safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver-diff@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" + integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY= + dependencies: + semver "^5.0.3" + +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.4.1, semver@^5.5.1: + version "5.7.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" + integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== + +semver@^6.0.0, semver@^6.1.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.2.0.tgz#4d813d9590aaf8a9192693d6c85b9344de5901db" + integrity sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A== + +semver@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +serialize-error@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a" + integrity sha1-ULZ51WNc34Rme9yOWa9OW4HV9go= + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +source-map-support@^0.5.12: + version "0.5.12" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" + integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spdx-correct@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" + integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" + integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz#75ecd1a88de8c184ef015eafb51b5b48bfd11bb1" + integrity sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stack-utils@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" + integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2", string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.1.0.tgz#ba846d1daa97c3c596155308063e075ed1c99aff" + integrity sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-bom-buf@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom-buf/-/strip-bom-buf-2.0.0.tgz#ff9c223937f8e7154b77e9de9bde094186885c15" + integrity sha512-gLFNHucd6gzb8jMsl5QmZ3QgnUJmp7qn4uUSHNwEXumAp7YizoGYw19ZUVfuq4aBOQUtyn2k8X/CwzWB73W2lQ== + dependencies: + is-utf8 "^0.2.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + +strip-indent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" + integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g= + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +supertap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supertap/-/supertap-1.0.0.tgz#bd9751c7fafd68c68cf8222a29892206a119fa9e" + integrity sha512-HZJ3geIMPgVwKk2VsmO5YHqnnJYl6bV5A9JW2uzqV43WmpgliNEYbuvukfor7URpaqpxuw3CfZ3ONdVbZjCgIA== + dependencies: + arrify "^1.0.1" + indent-string "^3.2.0" + js-yaml "^3.10.0" + serialize-error "^2.1.0" + strip-ansi "^4.0.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.0.0.tgz#f2392c50ab35bb3cae7beebf24d254a19f880c06" + integrity sha512-WRt32iTpYEZWYOpcetGm0NPeSvaebccx7hhS/5M6sAiqnhedtFCHFxkjzZlJvFNCPowiKSFGiZk5USQDFy83vQ== + dependencies: + has-flag "^4.0.0" + +symbol-observable@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + +term-size@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" + integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk= + dependencies: + execa "^0.7.0" + +throttleit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" + integrity sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw= + +time-zone@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/time-zone/-/time-zone-1.0.0.tgz#99c5bf55958966af6d06d83bdf3800dc82faec5d" + integrity sha1-mcW/VZWJZq9tBtg73zgA3IL67F0= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + +trim-newlines@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20" + integrity sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA= + +trim-off-newlines@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" + integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-detect@^4.0.0, type-detect@^4.0.5: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" + integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ== + +type-fest@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.5.2.tgz#d6ef42a0356c6cd45f49485c3b6281fc148e48a2" + integrity sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +uid2@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" + integrity sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I= + +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-value-ecmascript@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277" + integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57" + integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw== + +unique-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" + integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= + dependencies: + crypto-random-string "^1.0.0" + +unique-temp-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-temp-dir/-/unique-temp-dir-1.0.0.tgz#6dce95b2681ca003eebfb304a415f9cbabcc5385" + integrity sha1-bc6VsmgcoAPuv7MEpBX5y6vMU4U= + dependencies: + mkdirp "^0.5.1" + os-tmpdir "^1.0.1" + uid2 "0.0.3" + +update-notifier@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-3.0.1.tgz#78ecb68b915e2fd1be9f767f6e298ce87b736250" + integrity sha512-grrmrB6Zb8DUiyDIaeRTBCkgISYUgETNe7NglEbVsrLWXeESnlCSP50WfRSj/GmzMPl6Uchj24S/p80nP/ZQrQ== + dependencies: + boxen "^3.0.0" + chalk "^2.0.1" + configstore "^4.0.0" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.1.0" + is-npm "^3.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.0.0" + semver-diff "^2.0.0" + xdg-basedir "^3.0.0" + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + dependencies: + defaults "^1.0.3" + +well-known-symbols@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/well-known-symbols/-/well-known-symbols-2.0.0.tgz#e9c7c07dbd132b7b84212c8174391ec1f9871ba5" + integrity sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q== + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +widest-line@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" + integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA== + dependencies: + string-width "^2.1.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@^2.0.0: + version "2.4.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" + integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + +write-file-atomic@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.0.tgz#1b64dbbf77cb58fd09056963d63e62667ab4fb21" + integrity sha512-EIgkf60l2oWsffja2Sf2AL384dx328c0B+cIYPTQq5q2rOYuDV00/iPFBOUiDKKwKMOhkymH8AidPaRvzfxY+Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +xdg-basedir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" + integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= + +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= + +yargs-parser@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" + integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== + dependencies: + camelcase "^4.1.0" + +yauzl@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" + integrity sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU= + dependencies: + fd-slicer "~1.0.1" diff --git a/src/bindings/ejdb2_react_native/CMakeLists.txt b/src/bindings/ejdb2_react_native/CMakeLists.txt new file mode 100644 index 0000000..cb972ee --- /dev/null +++ b/src/bindings/ejdb2_react_native/CMakeLists.txt @@ -0,0 +1,10 @@ +file( + COPY . + DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + REGEX "(/\\.gradle)|(/node_modules)|(/build$)|(/libs)|(/jniLibs)" EXCLUDE +) + +add_subdirectory(binding) +if (BUILD_TESTS) + add_subdirectory(tests) +endif() diff --git a/src/bindings/ejdb2_react_native/README.md b/src/bindings/ejdb2_react_native/README.md new file mode 100644 index 0000000..ae75f6c --- /dev/null +++ b/src/bindings/ejdb2_react_native/README.md @@ -0,0 +1,76 @@ +# EJDB2 React Native binding + +Embeddable JSON Database engine http://ejdb.org Node.js binding. + +See https://github.com/Softmotions/ejdb/blob/master/README.md + + +## Note on versioning + +Since `2020-12-22` a new version scheme is used and the major version +of package has been incremented. Now package version is the concatenation +of ejdb2 core engine version and version serial number of this binding. +For example: given ejdb2 version `2.0.52` and binding serial `2` +the actual package version will be `2.0.522`. + +## Prerequisites + +- React native `0.61+` +- [Yarn](https://yarnpkg.com) package manager + +## Status + +- Android +- OSX not yet implemented, work in progress. + +## Getting started + +``` +yarn add ejdb2_react_native + +react-native link ejdb2_react_native +``` + +### Usage + +```js +import { EJDB2, JBE } from 'ejdb2_react_native'; + +// Simple query +const db = await EJDB2.open('hello.db'); +await db.createQuery('@mycoll/[foo = :?]') + .setString(0, 'bar') + .useExecute(doc => { + const doc = doc.json; + console.log(`Document: `, doc); +}); + +db.close(); +``` + +[See API docs](https://github.com/Softmotions/ejdb/blob/master/src/bindings/ejdb2_react_native/binding/index.d.ts) and [Tests](https://github.com/Softmotions/ejdb/blob/master/src/bindings/ejdb2_react_native/tests/App.js); + +## How build it manually + +```sh +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_REACT_NATIVE_BINDING=ON \ + -DANDROID_NDK_HOME= \ + -DANDROID_ABIS="x86;x86_64;arm64-v8a;armeabi-v7a" +``` + +## Testing + +```sh +mkdir build && cd build + +cmake .. -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_REACT_NATIVE_BINDING=ON \ + -DANDROID_NDK_HOME= \ + -DANDROID_ABIS="x86" \ + -DANDROID_AVD=TestingAVD \ + -DBUILD_TESTS=ON + +ctest +``` diff --git a/src/bindings/ejdb2_react_native/binding/.gitignore b/src/bindings/ejdb2_react_native/binding/.gitignore new file mode 100644 index 0000000..308adab --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/.gitignore @@ -0,0 +1,7 @@ +*.iml +/.idea +*.tgz +/yarn.lock +node_modules/ +/local.properties +/gradle* \ No newline at end of file diff --git a/src/bindings/ejdb2_react_native/binding/.npmignore b/src/bindings/ejdb2_react_native/binding/.npmignore new file mode 100644 index 0000000..16fefb7 --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/.npmignore @@ -0,0 +1,10 @@ +*.tgz +*.cmake +yarn.lock +/test +/CMakeLists.txt +/CMakeFiles +/Makefile +/android/build +/android/.gradle +!/android/libs diff --git a/src/bindings/ejdb2_react_native/binding/CHANGELOG.md b/src/bindings/ejdb2_react_native/binding/CHANGELOG.md new file mode 100644 index 0000000..f4cec7c --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/CHANGELOG.md @@ -0,0 +1,3 @@ +ejdb2_react_native (@EJDB2_RN_VERSION@) + +- Used new versioning scheme: {EJDB_VERSION}{BINDING_VERSION_NUMBER} diff --git a/src/bindings/ejdb2_react_native/binding/CMakeLists.txt b/src/bindings/ejdb2_react_native/binding/CMakeLists.txt new file mode 100644 index 0000000..728d9e6 --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/CMakeLists.txt @@ -0,0 +1,30 @@ +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/version.txt _VERSION) +set_property(GLOBAL PROPERTY EJDB2_RN_VERSION_PROPERTY "${PROJECT_VERSION}${_VERSION}") +set(EJDB2_RN_VERSION "${PROJECT_VERSION}${_VERSION}") + +if (NOT DEFINED REACT_NATIVE_PUB_DIR) + set(REACT_NATIVE_PUB_DIR ${CMAKE_CURRENT_BINARY_DIR}) +endif () + +find_program(YARN_EXEC yarn) +if (YARN_EXEC MATCHES "YARN_EXEC-NOTFOUND") + message(FATAL_ERROR "`yarn` executable not found") +endif () + +set(ANDROID_LIBS_DIR "${REACT_NATIVE_PUB_DIR}/android/libs") +foreach (AABI IN ITEMS ${ANDROID_ABIS}) + list(APPEND ANDROID_ABIS_LIBS "android_${AABI}") +endforeach () + +configure_file(package.json ${REACT_NATIVE_PUB_DIR}/package.json @ONLY) +configure_file(CHANGELOG.md ${REACT_NATIVE_PUB_DIR}/CHANGELOG.md @ONLY) + +add_custom_target( + ejdb2_react_native ALL + DEPENDS ${ANDROID_ABIS_LIBS} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/../../ejdb2_android/libs ${ANDROID_LIBS_DIR} + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/../README.md ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${YARN_EXEC} pack --filename ${REACT_NATIVE_PUB_DIR}/ejdb2_react_native.tgz + WORKING_DIRECTORY ${REACT_NATIVE_PUB_DIR} +) + diff --git a/src/bindings/ejdb2_react_native/binding/android/.gitignore b/src/bindings/ejdb2_react_native/binding/android/.gitignore new file mode 100644 index 0000000..ebc33b6 --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/android/.gitignore @@ -0,0 +1,4 @@ +/.idea +/libs +*.iml +/local.properties \ No newline at end of file diff --git a/src/bindings/ejdb2_react_native/binding/android/build.gradle b/src/bindings/ejdb2_react_native/binding/android/build.gradle new file mode 100644 index 0000000..f4f4cb7 --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/android/build.gradle @@ -0,0 +1,67 @@ +def _ext = rootProject.ext +def _compileSdkVersion = _ext.has('compileSdkVersion') ? _ext.compileSdkVersion : 26 +def _buildToolsVersion = _ext.has('buildToolsVersion') ? _ext.buildToolsVersion : '29.0.2' +def _minSdkVersion = _ext.has('minSdkVersion') ? _ext.minSdkVersion : 21 +def _targetSdkVersion = _ext.has('targetSdkVersion') ? _ext.targetSdkVersion : 26 + +buildscript { + repositories { + mavenLocal() + mavenCentral() + jcenter() + google() + } + dependencies { + classpath "com.android.tools.build:gradle:3.5.1" + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion _compileSdkVersion + buildToolsVersion _buildToolsVersion + + defaultConfig { + minSdkVersion _minSdkVersion + targetSdkVersion _targetSdkVersion + versionCode 1 + versionName "1.0" + } + + lintOptions { + abortOnError false + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + sourceSets.main { + jni.srcDirs = [] + jniLibs.srcDir 'libs' + } +} + +allprojects { + repositories { + mavenLocal() + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url("$rootDir/../node_modules/react-native/android") + } + maven { + // Android JSC is installed from npm + url("$rootDir/../node_modules/jsc-android/dist") + } + google() + jcenter() + } +} + + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'com.facebook.react:react-native:+' +} diff --git a/src/bindings/ejdb2_react_native/binding/android/gradle/wrapper/gradle-wrapper.jar b/src/bindings/ejdb2_react_native/binding/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5c2d1cf Binary files /dev/null and b/src/bindings/ejdb2_react_native/binding/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src/bindings/ejdb2_react_native/binding/android/gradle/wrapper/gradle-wrapper.properties b/src/bindings/ejdb2_react_native/binding/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2051d7c --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Oct 30 17:49:09 NOVT 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/src/bindings/ejdb2_react_native/binding/android/gradlew b/src/bindings/ejdb2_react_native/binding/android/gradlew new file mode 100755 index 0000000..b0d6d0a --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/android/gradlew @@ -0,0 +1,188 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/src/bindings/ejdb2_react_native/binding/android/gradlew.bat b/src/bindings/ejdb2_react_native/binding/android/gradlew.bat new file mode 100644 index 0000000..9991c50 --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/android/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/bindings/ejdb2_react_native/binding/android/src/main/.gitignore b/src/bindings/ejdb2_react_native/binding/android/src/main/.gitignore new file mode 100644 index 0000000..d88ac42 --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/android/src/main/.gitignore @@ -0,0 +1 @@ +/gen \ No newline at end of file diff --git a/src/bindings/ejdb2_react_native/binding/android/src/main/AndroidManifest.xml b/src/bindings/ejdb2_react_native/binding/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f4151e7 --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/android/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/src/bindings/ejdb2_react_native/binding/android/src/main/java/com/softmotions/ejdb2/EJDB2DBModule.java b/src/bindings/ejdb2_react_native/binding/android/src/main/java/com/softmotions/ejdb2/EJDB2DBModule.java new file mode 100644 index 0000000..c4056c4 --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/android/src/main/java/com/softmotions/ejdb2/EJDB2DBModule.java @@ -0,0 +1,275 @@ +package com.softmotions.ejdb2; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; + +public class EJDB2DBModule extends ReactContextBaseJavaModule { + + @SuppressWarnings("StaticCollection") + static Map dbmap = new ConcurrentHashMap<>(); + + static AtomicInteger dbkeys = new AtomicInteger(); + + private final Executor executor; + + public EJDB2DBModule(ReactApplicationContext reactContext, Executor executor) { + super(reactContext); + this.executor = executor; + } + + @Override + public String getName() { + return "EJDB2DB"; + } + + @ReactMethod + public void open(String path, ReadableMap opts, Promise promise) { + executor.execute(() -> openImpl(path, opts, promise)); + } + + private synchronized void openImpl(String path_, ReadableMap opts, Promise promise) { + try { + String path = normalizePath(path_); + DbEntry dbe = null; + for (DbEntry v : dbmap.values()) { + if (v.path.equals(path)) { + dbe = v; + break; + } + } + if (dbe != null) { + dbe.countOpen(); + promise.resolve(dbe.handle); + return; + } + // Now open new database + EJDB2Builder b = new EJDB2Builder(path); + if (opts.hasKey("readonly") && !opts.isNull("readonly") && opts.getBoolean("readonly")) + b.readonly(); + if (opts.hasKey("truncate") && !opts.isNull("truncate") && opts.getBoolean("truncate")) + b.truncate(); + if (opts.hasKey("wal_enabled") && !opts.isNull("wal_enabled")) + b.noWAL(!opts.getBoolean("wal_enabled")); + else + b.withWAL(); + if (opts.hasKey("wal_check_crc_on_checkpoint") && !opts.isNull("wal_check_crc_on_checkpoint")) + b.walCRCOnCheckpoint(opts.getBoolean("wal_check_crc_on_checkpoint")); + if (opts.hasKey("wal_checkpoint_buffer_sz") && !opts.isNull("wal_checkpoint_buffer_sz")) + b.walCheckpointBufferSize(opts.getInt("wal_checkpoint_buffer_sz")); + if (opts.hasKey("wal_checkpoint_timeout_sec") && !opts.isNull("wal_checkpoint_timeout_sec")) + b.walCheckpointTimeoutSec(opts.getInt("wal_checkpoint_timeout_sec")); + if (opts.hasKey("wal_savepoint_timeout_sec") && !opts.isNull("wal_savepoint_timeout_sec")) + b.walSavepointTimeoutSec(opts.getInt("wal_savepoint_timeout_sec")); + if (opts.hasKey("wal_wal_buffer_sz") && !opts.isNull("wal_wal_buffer_sz")) + b.walBufferSize(opts.getInt("wal_wal_buffer_sz")); + if (opts.hasKey("document_buffer_sz") && !opts.isNull("document_buffer_sz")) + b.documentBufferSize(opts.getInt("document_buffer_sz")); + if (opts.hasKey("sort_buffer_sz") && !opts.isNull("sort_buffer_sz")) + b.sortBufferSize(opts.getInt("sort_buffer_sz")); + + final Integer handle = dbkeys.incrementAndGet(); + dbmap.put(handle, new DbEntry(b.open(), handle, path)); + + promise.resolve(handle); + } catch (EJDB2Exception e) { + promise.reject(String.valueOf(e.getCode()), e.toString()); + } catch (Exception e) { + promise.reject(null, e.toString()); + } + } + + @ReactMethod + public synchronized void close(Integer handle, Promise promise) { + try { + if (db(handle).close()) + dbmap.remove(handle); + } catch (EJDB2Exception e) { + promise.reject(String.valueOf(e.getCode()), e.toString()); + } catch (Exception e) { + promise.reject(null, e.toString()); + } + promise.resolve(null); + } + + public void context(Promise promise) { + + } + + @ReactMethod + public void info(Integer handle, Promise promise) { + thread(handle, promise, db -> promise.resolve(db.infoAsString())); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public Integer createQuery(Integer handle, String query, String collection) { + return EJDB2JQLModule.createQuery(db(handle).db, query, collection); + } + + @ReactMethod + public void put(Integer handle, String collection, String json, Integer id, Promise promise) { + thread(handle, promise, db -> promise.resolve(String.valueOf(db.put(collection, json, id.longValue())))); + } + + @ReactMethod + public void patch(Integer handle, String collection, String json, Integer id, Promise promise) { + thread(handle, promise, db -> { + db.patch(collection, json, id.longValue()); + promise.resolve(null); + }); + } + + @ReactMethod + public void patchOrPut(Integer handle, String collection, String json, Integer id, Promise promise) { + thread(handle, promise, db -> { + db.patchOrPut(collection, json, id.longValue()); + promise.resolve(null); + }); + } + + @ReactMethod + public void get(Integer handle, String collection, Integer id, Promise promise) { + thread(handle, promise, db -> promise.resolve(db.getAsString(collection, id.longValue()))); + } + + @ReactMethod + public void del(Integer handle, String collection, Integer id, Promise promise) { + thread(handle, promise, db -> { + db.del(collection, id.longValue()); + promise.resolve(null); + }); + } + + @ReactMethod + public void renameCollection(Integer handle, String oldCollectionName, String newCollectionName, Promise promise) { + thread(handle, promise, db -> { + db.renameCollection(oldCollectionName, newCollectionName); + promise.resolve(null); + }); + } + + @ReactMethod + public void removeCollection(Integer handle, String collection, Promise promise) { + thread(handle, promise, db -> { + db.removeCollection(collection); + promise.resolve(null); + }); + } + + @ReactMethod + public void ensureStringIndex(Integer handle, String collection, String path, Boolean unique, Promise promise) { + thread(handle, promise, db -> { + db.ensureStringIndex(collection, path, unique); + promise.resolve(null); + }); + } + + @ReactMethod + public void removeStringIndex(Integer handle, String collection, String path, Boolean unique, Promise promise) { + thread(handle, promise, db -> { + db.removeStringIndex(collection, path, unique); + promise.resolve(null); + }); + } + + @ReactMethod + public void ensureIntIndex(Integer handle, String collection, String path, Boolean unique, Promise promise) { + thread(handle, promise, db -> { + db.ensureIntIndex(collection, path, unique); + promise.resolve(null); + }); + } + + @ReactMethod + public void removeIntIndex(Integer handle, String collection, String path, Boolean unique, Promise promise) { + thread(handle, promise, db -> { + db.removeIntIndex(collection, path, unique); + promise.resolve(null); + }); + } + + @ReactMethod + public void ensureFloatIndex(Integer handle, String collection, String path, Boolean unique, Promise promise) { + thread(handle, promise, db -> { + db.ensureFloatIndex(collection, path, unique); + promise.resolve(null); + }); + } + + @ReactMethod + public void removeFloatIndex(Integer handle, String collection, String path, Boolean unique, Promise promise) { + thread(handle, promise, db -> { + db.removeFloatIndex(collection, path, unique); + promise.resolve(null); + }); + } + + @ReactMethod + public void onlineBackup(Integer handle, String targetFile, Promise promise) { + thread(handle, promise, db -> { + promise.resolve(String.valueOf(db.onlineBackup(targetFile))); + }); + } + + private DbEntry db(Integer handle) throws RuntimeException { + DbEntry dbe = dbmap.get(handle); + if (dbe == null) + throw new IllegalStateException("Database handle already disposes"); + return dbe; + } + + private void immediate(Integer handle, Promise promise, DbLogic fn) { + try { + fn.apply(db(handle).db); + } catch (EJDB2Exception e) { + promise.reject(String.valueOf(e.getCode()), e.toString()); + } catch (Exception e) { + promise.reject(null, e.toString()); + } + } + + private void thread(Integer handle, Promise promise, DbLogic fn) { + executor.execute(() -> immediate(handle, promise, fn)); + } + + private String normalizePath(String path) { + return this.getReactApplicationContext().getDatabasePath(path).getAbsolutePath(); + } + + private interface DbLogic { + void apply(EJDB2 db) throws Exception; + } + + private static class DbEntry { + DbEntry(EJDB2 db, Integer handle, String path) { + this.db = db; + this.counter = new AtomicInteger(1); + this.path = path; + this.handle = handle; + } + + final EJDB2 db; + private final String path; + private final Integer handle; + private final AtomicInteger counter; + + void countOpen() { + this.counter.incrementAndGet(); + } + + boolean close() { + int cnt = this.counter.decrementAndGet(); + if (cnt <= 0) + db.close(); + return cnt <= 0; + } + } +} \ No newline at end of file diff --git a/src/bindings/ejdb2_react_native/binding/android/src/main/java/com/softmotions/ejdb2/EJDB2JQLModule.java b/src/bindings/ejdb2_react_native/binding/android/src/main/java/com/softmotions/ejdb2/EJDB2JQLModule.java new file mode 100644 index 0000000..f4eb1cc --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/android/src/main/java/com/softmotions/ejdb2/EJDB2JQLModule.java @@ -0,0 +1,281 @@ +package com.softmotions.ejdb2; + +import java.io.UnsupportedEncodingException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.core.DeviceEventManagerModule; + +public class EJDB2JQLModule extends ReactContextBaseJavaModule { + + @SuppressWarnings("StaticCollection") + static Map jqlmap = new ConcurrentHashMap<>(); + + static final AtomicInteger jqlkeys = new AtomicInteger(0); + + private final Executor executor; + + public EJDB2JQLModule(ReactApplicationContext reactContext, Executor executor) { + super(reactContext); + this.executor = executor; + } + + @Override + public String getName() { + return "EJDB2JQL"; + } + + public static Integer createQuery(EJDB2 db, String query, String collection) throws EJDB2Exception { + Integer id = jqlkeys.getAndIncrement(); + jqlmap.put(id, db.createQuery(query, collection)); + return id; + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public String getQuery(Integer jqlid) { + return jql(jqlid).getQuery(); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public String getCollection(Integer jqlid) { + return jql(jqlid).getCollection(); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void setCollection(Integer jqlid, String collection) { + jql(jqlid).setCollection(collection); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void withExplain(Integer jqlid) { + jql(jqlid).withExplain(); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public String getExplainLog(Integer jqlid) { + try { + return jql(jqlid).getExplainLog(); + } catch (UnsupportedEncodingException ignored) { + return null; + } + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void setSkip(Integer jqlid, String val) { + jql(jqlid).setSkip(Long.parseLong(val)); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public String getSkip(Integer jqlid) { + return String.valueOf(jql(jqlid).getSkip()); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void setLimit(Integer jqlid, String val) { + jql(jqlid).setLimit(Long.parseLong(val)); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public String getLimit(Integer jqlid) { + return String.valueOf(jql(jqlid).getLimit()); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void psetString(Integer jqlid, Integer placeholder, String val) { + jql(jqlid).setString(placeholder, val); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void setString(Integer jqlid, String placeholder, String val) { + jql(jqlid).setString(placeholder, val); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void psetLong(Integer jqlid, Integer placeholder, String val) { + jql(jqlid).setLong(placeholder, Long.parseLong(val)); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void setLong(Integer jqlid, String placeholder, String val) { + jql(jqlid).setLong(placeholder, Long.parseLong(val)); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void psetJSON(Integer jqlid, Integer placeholder, String val) { + jql(jqlid).setJSON(placeholder, val); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void setJSON(Integer jqlid, String placeholder, String val) { + jql(jqlid).setJSON(placeholder, val); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void psetRegexp(Integer jqlid, Integer placeholder, String val) { + jql(jqlid).setRegexp(placeholder, val); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void setRegexp(Integer jqlid, String placeholder, String val) { + jql(jqlid).setRegexp(placeholder, val); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void psetDouble(Integer jqlid, Integer placeholder, Double val) { + jql(jqlid).setDouble(placeholder, val); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void setDouble(Integer jqlid, String placeholder, Double val) { + jql(jqlid).setDouble(placeholder, val); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void psetBoolean(Integer jqlid, Integer placeholder, Boolean val) { + jql(jqlid).setBoolean(placeholder, val); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void setBoolean(Integer jqlid, String placeholder, Boolean val) { + jql(jqlid).setBoolean(placeholder, val); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void psetNull(Integer jqlid, Integer placeholder) { + jql(jqlid).setNull(placeholder); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void setNull(Integer jqlid, String placeholder) { + jql(jqlid).setNull(placeholder); + } + + @ReactMethod + public void execute(Integer jqlid, String eventId, Promise promise) { + final ReactContext ctx = getReactApplicationContext(); + final DeviceEventManagerModule.RCTDeviceEventEmitter module = ctx + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class); + + AtomicReference holder = new AtomicReference<>(Arguments.createArray()); + + thread(jqlid, promise, jql -> { + jql.execute((id, json) -> { + WritableArray wa = holder.get(); + if (wa.size() >= 64) { + module.emit(eventId, wa); + wa = Arguments.createArray(); + holder.set(wa); + } + WritableMap map = Arguments.createMap(); + map.putString("id", String.valueOf(id)); + map.putString("raw", json); + wa.pushMap(map); + return 1; + }); + WritableArray wa = holder.get(); + wa.pushNull(); + holder.set(null); + module.emit(eventId, wa); + promise.resolve(null); + }); + } + + @ReactMethod + public void list(Integer jqlid, ReadableMap opts, Promise promise) { + thread(jqlid, promise, jql -> { + if (opts.hasKey("skip")) { + jql.setSkip(Long.parseLong(opts.getString("skip"))); + } + if (opts.hasKey("limit")) { + jql.setLimit(Long.parseLong(opts.getString("limit"))); + } + WritableArray ret = Arguments.createArray(); + jql.execute((id, json) -> { + WritableMap map = Arguments.createMap(); + map.putString("id", String.valueOf(id)); + map.putString("raw", json); + ret.pushMap(map); + return 1; + }); + promise.resolve(ret); + }); + } + + @ReactMethod + public void first(Integer jqlid, Promise promise) { + thread(jqlid, promise, jql -> { + Map.Entry f = jql.first(); + if (f.getValue() != null) { + WritableMap map = Arguments.createMap(); + map.putInt("id", f.getKey().intValue()); + map.putString("raw", f.getValue()); + promise.resolve(map); + } else { + promise.resolve(null); + } + }); + } + + @ReactMethod + public void executeScalarInt(Integer jqlid, Promise promise) { + thread(jqlid, promise, jql -> promise.resolve(String.valueOf(jql.executeScalarInt()))); + } + + @ReactMethod + public void reset(Integer jqlid, Promise promise) { + immediate(jqlid, promise, jql -> { + jql.reset(); + promise.resolve(null); + }); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public void close(Integer jqlid) { + JQL jql = jqlmap.get(jqlid); + if (jql != null) { + jqlmap.remove(jqlid); + try { + jql.close(); + } catch (Exception ignored) { + } + } + } + + private JQL jql(Integer jqlid) throws RuntimeException { + JQL jql = jqlmap.get(jqlid); + if (jql == null) { + throw new IllegalStateException("Query handle already disposed"); + } + return jql; + } + + private void immediate(Integer jqlid, Promise promise, JqlLogic fn) { + try { + fn.apply(jql(jqlid)); + } catch (EJDB2Exception e) { + promise.reject(String.valueOf(e.getCode()), e.toString()); + } catch (Exception e) { + promise.reject(null, e.toString()); + } + } + + private void thread(Integer jqlid, Promise promise, JqlLogic fn) { + executor.execute(() -> immediate(jqlid, promise, fn)); + } + + private interface JqlLogic { + void apply(JQL jql) throws Exception; + } +} \ No newline at end of file diff --git a/src/bindings/ejdb2_react_native/binding/android/src/main/java/com/softmotions/ejdb2/EJDB2Package.java b/src/bindings/ejdb2_react_native/binding/android/src/main/java/com/softmotions/ejdb2/EJDB2Package.java new file mode 100644 index 0000000..c857e95 --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/android/src/main/java/com/softmotions/ejdb2/EJDB2Package.java @@ -0,0 +1,37 @@ + +package com.softmotions.ejdb2; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +public class EJDB2Package implements ReactPackage { + + private static final Executor executor = new ThreadPoolExecutor(0, 5, 60L, TimeUnit.SECONDS, + new SynchronousQueue()); + + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + return Arrays.asList(new EJDB2DBModule(reactContext, executor), new EJDB2JQLModule(reactContext, executor)); + } + + // Deprecated from RN 0.47 + public List> createJSModules() { + return Collections.emptyList(); + } + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} \ No newline at end of file diff --git a/src/bindings/ejdb2_react_native/binding/index.d.ts b/src/bindings/ejdb2_react_native/binding/index.d.ts new file mode 100644 index 0000000..e470a79 --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/index.d.ts @@ -0,0 +1,507 @@ +/************************************************************************************************** + * EJDB2 + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +declare namespace ejdb2_react_native { + export type Placeholder = string | number; + + export type JQLExecuteCallback = (doc: JBDOC, jql?: JQL) => any; + + interface JQLUseQueryCallback { + (jql: JQL): Promise; + } + + /** + * EJDB2 Error helpers. + */ + export class JBE { + /** + * Returns `true` if given error [err] is `IWKV_ERROR_NOTFOUND` + * @param err + */ + static isNotFound(err: any): boolean; + + /** + * Returns `true` if given errror [err] is `JQL_ERROR_QUERY_PARSE` + * @param {Error} err + * @return {boolean} + */ + static isInvalidQuery(err: any): boolean; + } + + /** + * Database open options. + */ + interface OpenOptions { + /** + * Open databas in read-only mode. + */ + readonly?: boolean; + + /** + * Truncate database file on open. + */ + truncate?: boolean; + + /** + * Enable WAL. Default: true. + */ + wal_enabled?: boolean; + + /** + * Check CRC32 sum for every WAL checkpoint. + * Default: false. + */ + wal_check_crc_on_checkpoint?: boolean; + + /** + * Size of checkpoint buffer in bytes. + * Default: 1Gb. Android: 64Mb + */ + wal_checkpoint_buffer_sz?: number; + + /** + * WAL buffer size in bytes. + * Default: 8Mb. Android: 2Mb. + */ + wal_wal_buffer_sz?: number; + + /** + * Checkpoint timeout in seconds. + * Default: 300. Android: 60. + */ + wal_checkpoint_timeout_sec?: number; + + /** + * Savepoint timeout in secods. + * Default: 10. + */ + wal_savepoint_timeout_sec?: number; + + /** + * Initial size of buffer in bytes used to process/store document on queries. + * Preferable average size of document. + * Default: 65536. Minimal: 16384. + */ + document_buffer_sz?: number; + + /** + * Max sorting buffer size in bytes. + * If exceeded, an overflow temp file for data will be created. + * Default: 16777216. Minimal: 1048576 + */ + sort_buffer_sz?: number; + } + + /** + * Query execution options. + */ + interface QueryOptions { + /** + * Overrides `limit` encoded in query + */ + limit?: number | string; + + /** + * Overrides `skip` encoded in query + */ + skip?: number | string; + } + + /** + * Collection index descriptor. + */ + interface IndexDescriptor { + /** + * rfc6901 JSON pointer to indexed field. + */ + ptr: string; + + /** + * Index mode as bit mask: + * + * - 0x01 EJDB_IDX_UNIQUE Index is unique + * - 0x04 EJDB_IDX_STR Index for JSON string field value type + * - 0x08 EJDB_IDX_I64 Index for 8 bytes width signed integer field values + * - 0x10 EJDB_IDX_F64 Index for 8 bytes width signed floating point field values. + */ + mode: number; + + /** + * Index flags. See iwkv.h#iwdb_flags_t + */ + idbf: number; + + /** + * Internal index database identifier. + */ + dbid: number; + + /** + * Number of indexed records. + */ + rnum: number; + } + + /** + * Collection descriptor. + */ + interface CollectionDescriptor { + /** + * Name of collection. + */ + name: string; + + /** + * Internal database identifier. + */ + dbid: number; + + /** + * Number of documents stored in collection. + */ + rnum: number; + + /** + * List of collection indexes. + */ + indexes: Array; + } + + interface EJDB2Info { + /** + * Database engine version string. + * Eg. "2.0.29" + */ + version: string; + + /** + * Database file path. + */ + file: string; + + /** + * Database file size in bytes. + */ + size: number; + + /** + * List of database collections. + */ + collections: Array; + } + + /** + * EJDB2 document. + */ + interface JBDOC { + /* + * Document identifier + */ + id: number; + + /** + * Document JSON object + */ + json: any; + + /** + * String represen + */ + toString(): string; + } + + /** + * Database wrapper. + */ + export class EJDB2 { + /** + * Open database identified by relative `filename` in context of + * React Native database directory used for sqlite db. + * + * @param filename `filename` in context of RN sqlite database directory + * @param opts Database mode options + */ + static open(name: string, opts?: OpenOptions): EJDB2; + + /** + * Closes database instance. + */ + close(): Promise; + + /** + * Get json body with database metadata object. + */ + info(): Promise; + + /** + * Create instance of [query] specified for [collection]. + * If [collection] is not specified a [query] spec must contain collection name, + * eg: `@mycollection/[foo=bar]` + * + * @note WARNING In order to avoid memory leaks, dispose created query object by {@link JQL#close} + * or use {@link JQL#use} + * + * @param query Query text + * @param collection Optional collection used in query + * @returns {JQL} + */ + createQuery(query: string, collection?: string): JQL; + + /** + * Saves [json] document under specified [id] or create a document + * with new generated `id`. Returns promise holding actual document `id`. + */ + put(collection: String, json: object | string, id?: number): Promise; + + /** + * Apply rfc6902/rfc7386 JSON [patch] to the document identified by [id]. + */ + patch(collection: string, json: object | string, id: number): Promise; + + /** + * Apply JSON merge patch (rfc7396) to the document identified by `id` or + * insert new document under specified `id`. + */ + patchOrPut(collection: string, json: object | string, id: number): Promise; + + /** + * Get json body of document identified by [id] and stored in [collection]. + * + * If document with given `id` is not found then `Error` will be thrown. + * Not found error can be detected by {@link JBE.isNotFound} + */ + get(collection: string, id: number): Promise; + + /** + * Get json body of document identified by [id] and stored in [collection]. + * + * If document with given `id` is not found then `null` will be resoved. + */ + getOrNull(collection: string, id: number): Promise; + + /** + * Removes document idenfied by [id] from [collection]. + * + * If document with given `id` is not found then `Error` will be thrown. + * Not found error can be detected by {@link JBE.isNotFound} + */ + del(collection: string, id: number): Promise; + + /** + * Renames collection + */ + renameCollection(oldCollectionName: string, newCollectionName: string): Promise; + + /** + * Ensures json document database index specified by [path] json pointer to string data type. + */ + ensureStringIndex(collection: string, path: string, unique?: boolean): Promise; + + /** + * Removes specified database index. + */ + removeStringIndex(collection: string, path: string, unique?: boolean): Promise; + + /** + * Ensures json document database index specified by [path] json pointer to integer data type. + */ + ensureIntIndex(collection: string, path: string, unique?: boolean): Promise; + + /** + * Removes specified database index. + */ + removeIntIndex(collection: string, path: string, unique?: boolean): Promise; + + /** + * Ensures json document database index specified by [path] json pointer to floating point data type. + */ + ensureFloatIndex(collection: string, path: string, unique?: boolean): Promise; + + /** + * Removes specified database index. + */ + removeFloatIndex(collection: string, path: string, unique?: boolean): Promise; + + /** + * Removes database [collection]. + */ + removeCollection(collection: string): Promise; + + /** + * Creates an online database backup image and copies it into the specified [fileName]. + * During online backup phase read/write database operations are allowed and not + * blocked for significant amount of time. Returns promise with backup + * finish time as number of milliseconds since epoch. + */ + onlineBackup(fileName: string): Promise; + } + + /** + * EJDB Query. + */ + interface JQL { + /** + * Database instance associated with this query. + */ + readonly db: EJDB2; + + /** + * Query text. + */ + readonly query: string; + + /** + * Retrieve query execution plan. + */ + readonly explainLog: string | null; + + /** + * Number of documents to skip. + */ + readonly skip: number; + + /** + * Maximum number of documents to fetch. + */ + readonly limit: number; + + /** + * Collection name used in query + */ + readonly collection: string; + + /** + * Set number of documents to skip. + * Specified value overrides `skip` option encoded in query. + * @param {number} skip + */ + withSkip(skip: number): JQL; + + /** + * Set maximum number of documents to fetch. + * Specified value overrides `limit` option encoded in query. + * @param {number} limit Limit + */ + withLimit(limit: number): JQL; + + /** + * Set collection to use with query. + * Value overrides collection encoded in query. + */ + withCollection(coll: string): JQL; + + /** + * Turn on explain query mode + */ + withExplain(): JQL; + + /** + * Set string [val] at the specified [placeholder]. + */ + setString(placeholder: Placeholder, val: string): JQL; + + /** + * Set [json] at the specified [placeholder]. + */ + setJSON(placeholder: Placeholder, val: string | object): JQL; + + /** + * Set [regexp] string at the specified [placeholder]. + * @note `RegExp` options are ignored. + */ + setRegexp(placeholder: Placeholder, val: string | RegExp): JQL; + + /** + * Set number [val] at the specified [placeholder]. + */ + setNumber(placeholder: Placeholder, val: number): JQL; + + /** + * Set boolean [val] at the specified [placeholder]. + */ + setBoolean(placeholder: Placeholder, val: boolean): JQL; + + /** + * Set `null` at the specified [placeholder]. + */ + setNull(placeholder: Placeholder): JQL; + + /** + * Execute this query. + */ + execute(dispose: boolean, callback: JQLExecuteCallback): Promise; + + /** + * Execute this query then dispose it. + */ + useExecute(callback: JQLExecuteCallback): Promise; + + /** + * Uses query in `callback` then closes it. + * @param callback Function accepts `JQL` query object. + * @return `usefn` promise value. + */ + use(callback: JQLUseQueryCallback): Promise; + + /** + * List matched document. + * @note Use it with care to avoid wasting of memory. + * See {@link JQL#use} for result-set callback API. + * @param opts + */ + list(opts: QueryOptions): Promise; + + /** + * Collects up to [n] documents from result set into array. + * @param n Default: 1 + */ + firstN(n?: number): Promise; + + /** + * Returns a first record in result set. + * If no reconds found `null` resolved promise will be returned. + */ + first(): Promise; + + /** + * Returns a scalar integer value as result of query execution. + * Eg.: A count query: `/... | count` + */ + scalarInt(): Promise; + + /** + * Reset query parameters. + */ + reset(): Promise; + + /** + * Disposes JQL instance and releases all underlying resources. + */ + close(): void; + } +} + +export = ejdb2_react_native; diff --git a/src/bindings/ejdb2_react_native/binding/index.js b/src/bindings/ejdb2_react_native/binding/index.js new file mode 100644 index 0000000..41cc596 --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/index.js @@ -0,0 +1,669 @@ +import { NativeEventEmitter, NativeModules } from 'react-native'; + +const { EJDB2DB: ejdb2, EJDB2JQL: ejdb2jql } = NativeModules; + +/** + * EJDB2 Error helpers. + */ +class JBE { + /** + * Returns `true` if given error [err] is `IWKV_ERROR_NOTFOUND` + * @param {Error} err + * @returns {boolean} + */ + static isNotFound(err) { + const msg = (err.message || '').toString(); + return msg.indexOf('@ejdb IWRC:75001') != -1; + } + + static isInvalidQuery(err) { + const msg = (err.message || '').toString(); + return msg.indexOf('@ejdb IWRC:87001') != -1; + } +} + +/** + * EJDB document. + */ +class JBDOC { + /** + * Get document JSON object + */ + get json() { + if (this._json != null) { + return this._json; + } + this._json = JSON.parse(this._raw); + this._raw = null; + return this._json; + } + + /** + * @param {number} id Document ID + * @param {string} raw Document JSON as string + */ + constructor(id, raw) { + this.id = id; + this._raw = raw; + this._json = null; + } + + toString() { + return `JBDOC: ${this.id} ${this._raw != null ? this._raw : JSON.stringify(this.json)}`; + } +} + +/** + * EJDB Query. + */ +class JQL { + /** + * @param db {EJDB2} + * @param jql + */ + constructor(db, jql) { + this._db = db; + this._jql = jql; + this._nee = new NativeEventEmitter(ejdb2jql); + this._eid = 0; + this._explain = null; + this._withExplain = false; + } + + /** + * Get database instance associated with this query. + * @returns {EJDB2} + */ + get db() { + return this._db; + } + + /** + * Get query text + * @return {string} + */ + get query() { + return ejdb2jql.getQuery(this._jql); + } + + /** + * Get documents collection name used in query. + * @return {string} + */ + get collection() { + return ejdb2jql.getCollection(this._jql); + } + + /** + * Set collection to use with query. + * Value overrides collection encoded in query. + * @param coll + * @return {JQL} + */ + withCollection(coll) { + ejdb2jql.setCollection(this._jql, coll); + return this; + } + + /** + * Retrieve query execution plan. + * @return {string|null} + */ + get explainLog() { + return this._explainLog || ejdb2jql.getExplainLog(this._jql); + } + + /** + * Get number of documents to skip. + * @return {number} + */ + get skip() { + return parseInt(ejdb2jql.getSkip(this._jql)); + } + + /** + * Set number of documents to skip. + * Specified value overrides `skip` option encoded in query. + * @param {number} skip + * @return {JQL} + */ + withSkip(skip) { + if (!this._isInteger(skip)) { + throw new Error('`skip` argument must an Integer'); + } + ejdb2jql.setSkip(this._jql, skip.toString()); + return this; + } + + /** + * Get maximum number of documents to fetch. + * @return {number} + */ + get limit() { + return parseInt(ejdb2jql.getLimit(this._jql)); + } + + /** + * Set maximum number of documents to fetch. + * Specified value overrides `limit` option encoded in query. + * @param {number} limit Limit + * @return {JQL} + */ + withLimit(limit) { + if (!this._isInteger(limit)) { + new Error('`limit` argument must an Integer'); + } + ejdb2jql.setLimit(this._jql, limit.toString()); + return this; + } + + /** + * Turn on explain query mode + * @return {JQL} + */ + withExplain() { + ejdb2jql.withExplain(this._jql); + this._withExplain = true; + this._explainLog = null; + return this; + } + + /** + * Set string [val] at the specified [placeholder]. + * @param {string|number} placeholder + * @param {string} val + * @return {JQL} + */ + setString(placeholder, val) { + if (val != null && typeof val !== 'string') { + val = val.toString(); + } + this._p('setString', placeholder)(this._jql, placeholder, val); + return this; + } + + /** + * Set [json] at the specified [placeholder]. + * @param {string|number} placeholder + * @param {string|object} val + * @return {JQL} + */ + setJSON(placeholder, val) { + if (typeof val !== 'string') { + val = JSON.stringify(val); + } + this._p('setJSON', placeholder)(this._jql, placeholder, val); + return this; + } + + /** + * Set [regexp] string at the specified [placeholder]. + * @param {string|number} placeholder + * @param {string|RegExp} val + * @return {JQL} + */ + setRegexp(placeholder, val) { + if (val instanceof RegExp) { + const sval = val.toString(); + val = sval.substring(1, sval.lastIndexOf('/')); + } else if (typeof val !== 'string') { + throw new Error('Regexp argument must be a string or RegExp object'); + } + this._p('setRegexp', placeholder)(this._jql, placeholder, val); + return this; + } + + /** + * Set number [val] at the specified [placeholder]. + * @param {string|number} placeholder + * @param {number} val + * @returns {JQL} + */ + setNumber(placeholder, val) { + if (typeof val !== 'number') { + throw new Error('Value must be a number'); + } + if (this._isInteger(val)) { + this._p('setLong', placeholder)(this._jql, placeholder, val.toString()); + } else { + this._p('setDouble', placeholder)(this._jql, placeholder, val); + } + return this; + } + + /** + * Set boolean [val] at the specified [placeholder]. + * @param {string|number} placeholder + * @param {boolean} val + * @return {JQL} + */ + setBoolean(placeholder, val) { + this._p('setBoolean', placeholder)(this._jql, placeholder, !!val); + return this; + } + + /** + * Set `null` at the specified [placeholder]. + * @param {string|number} placeholder + * @return {JQL} + */ + setNull(placeholder) { + this._p('setNull', placeholder)(this._jql, placeholder); + return this; + } + + /** + * @callback JQLExecuteCallback + * @param {JBDOC} doc + * @param {JQL} jql + */ + /** + * Execute this query. + * + * @param {boolean} dispose Dispose this query after execution + * @param {JQLExecuteCallback} callback + * @return {Promise} + */ + execute(dispose, callback) { + const eid = this._nextEventId(); + const reg = this._nee.addListener(eid, data => { + data.forEach(row => { + if (row != null) { + callback && callback(new JBDOC(row.id, row.raw), this); + } + }); + if (data.length && data[data.length - 1] == null) { + // EOF last element is null + reg.remove(); + } + }); + this._explainLog = null; + return ejdb2jql + .execute(this._jql, eid) + .catch(err => { + reg.remove(); + return err; + }) + .then(err => { + if (this._withExplain) { + // Save explain log before close + this._explainLog = ejdb2jql.getExplainLog(this._jql); + } + if (dispose) { + this.close(); + } + if (err) { + return Promise.reject(err); + } + }); + } + + /** + * Execute this query then dispose it. + * + * @param {JQLExecuteCallback?} callback + * @return {Promise} + */ + useExecute(callback) { + return this.execute(true, callback); + } + + /** + * Uses query in `callback` then closes it. + * @param {UseQueryCallback} callback Function accepts query object + * @return {Promise<*>} + */ + async use(callback) { + let ret; + try { + this._explainLog = null; + ret = await callback(this); + } finally { + if (this._withExplain) { + // Save explain log before close + this._explainLog = ejdb2jql.getExplainLog(this._jql); + } + this.close(); + } + return ret; + } + + /** + * + * @param {object?} opts Optional options object. + * - `limit` Override maximum number of documents in result set + * - `skip` Override skip number of documents to skip + * @return {Promise} + */ + list(opts_ = {}) { + const opts = { ...opts_ }; + if (opts.limit != null && typeof opts.limit !== 'string') { + opts.limit = `${opts.limit}`; + } + if (opts.skip != null && typeof opts.skip !== 'string') { + opts.skip = `${opts.skip}`; + } + return ejdb2jql.list(this._jql, opts).then(l => l.map(data => new JBDOC(data.id, data.raw))); + } + + /** + * Collects up to [n] documents from result set into array. + * @param {number|string} n Upper limit of documents in result set + * @return {Promise} + */ + firstN(n = 1) { + return this.list({ limit: n }); + } + + /** + * Returns a first record in result set. + * If no reconds found `null` resolved promise will be returned. + * @returns {Promise} + */ + first() { + return ejdb2jql.first(this._jql).then(data => (data ? new JBDOC(data.id, data.raw) : null)); + } + + /** + * Returns a scalar integer value as result of query execution. + * Eg.: A count query: `/... | count` + * @return {Promise} + */ + scalarInt() { + return ejdb2jql.executeScalarInt(this._jql).then(r => parseInt(r)); + } + + /** + * Reset query parameters. + * @returns {Promise} + */ + reset() { + return ejdb2jql.reset(this._jql).then(_ => this); + } + + /** + * Disposes JQL instance and releases all underlying resources. + */ + close() { + return ejdb2jql.close(this._jql); + } + + /** + * @private + */ + _isInteger(n) { + return n === +n && n === (n | 0); + } + + /** + * @private + */ + _p(name, placeholder) { + const t = typeof placeholder; + if (!(t === 'number' || t === 'string')) { + throw new Error('Invalid placeholder specified, must be either string or number'); + } + return ejdb2jql[(this._isInteger(placeholder) ? 'p' : '') + name]; + } + + /** + * Generate next event id. + * @private + * @return {string} + */ + _nextEventId() { + return `jql${this._eid++}`; + } +} + +/** + * EJDB2 React Native wrapper. + */ +class EJDB2 { + /** + * Open database instance. + * + * @param {string} path Path to database + * @param {Object} [opts] + * @return {Promise} EJDB2 instance promise + */ + static open(path, opts = {}) { + return ejdb2.open(path, opts).then(db => new EJDB2(db)); + } + + constructor(db) { + this._db = db; + } + + /** + * Closes database instance. + * @return {Promise} + */ + close() { + return ejdb2.close(this._db); + } + + /** + * Get json body with database metadata. + * + * @return {Promise} + */ + info() { + return ejdb2.info(this._db).then(JSON.parse); + } + + /** + * @callback UseQueryCallback + * @param {JQL} query + * @return {Promise<*>} + */ + + /** + * Create instance of [query] specified for [collection]. + * If [collection] is not specified a [query] spec must contain collection name, + * eg: `@mycollection/[foo=bar]` + * + * @note WARNING In order to avoid memory leaks, dispose created query object by {@link JQL#close} + * or use {@link JQL#use} + * + * @param {string} query Query text + * @param {string} [collection] + * @returns {JQL} + */ + createQuery(query, collection) { + const qh = ejdb2.createQuery(this._db, query, collection); + return new JQL(this, qh); + } + + /** + * Saves [json] document under specified [id] or create a document + * with new generated `id`. Returns promise holding actual document `id`. + * + * @param {string} collection + * @param {Object|string} json + * @param {number} [id] + * @returns {Promise} + */ + put(collection, json, id = 0) { + json = typeof json === 'string' ? json : JSON.stringify(json); + return ejdb2.put(this._db, collection, json, id).then(v => parseInt(v)); + } + + /** + * Apply rfc6902/rfc7386 JSON [patch] to the document identified by [id]. + * + * @param {string} collection + * @param {Object|string} json + * @param {number} id + * @return {Promise} + */ + patch(collection, json, id) { + json = typeof json === 'string' ? json : JSON.stringify(json); + return ejdb2.patch(this._db, collection, json, id); + } + + /** + * Apply JSON merge patch (rfc7396) to the document identified by `id` or + * insert new document under specified `id`. + * + * @param {String} collection + * @param {Object|string} json + * @param {number} id + * @return {Promise} + */ + patchOrPut(collection, json, id) { + json = typeof json === 'string' ? json : JSON.stringify(json); + return ejdb2.patchOrPut(this._db, collection, json, id); + } + + /** + * Get json body of document identified by [id] and stored in [collection]. + * + * @param {string} collection + * @param {number} id + * @return {Promise} JSON object + */ + get(collection, id) { + return ejdb2.get(this._db, collection, id).then(JSON.parse); + } + + /** + * Get json body of document identified by [id] and stored in [collection]. + * If document with given `id` is not found then `null` will be resoved. + * + * @param {string} collection + * @param {number} id + * @return {Promise} JSON object + */ + getOrNull(collection, id) { + return this.get(collection, id).catch((err) => { + if (JBE.isNotFound(err)) { + return null; + } else { + return Promise.reject(err); + } + }); + } + + /** + * Removes document idenfied by [id] from [collection]. + * + * @param {string} collection + * @param {number} id + * @return {Promise} + */ + del(collection, id) { + return ejdb2.del(this._db, collection, id); + } + + /** + * Renames collection. + * + * @param {string} oldCollectionName Collection to be renamed + * @param {string} newCollectionName New name of collection + * @return {Promise} + */ + renameCollection(oldCollectionName, newCollectionName) { + return ejdb2.renameCollection(this._db, oldCollectionName, newCollectionName); + } + + /** + * Removes database [collection]. + * + * @param {string} collection + * @return {Promise} + */ + removeCollection(collection) { + return ejdb2.removeCollection(this._db, collection).then(_ => this); + } + + /** + * Ensures json document database index specified by [path] json pointer to string data type. + * + * @param {string} collection + * @param {string} path + * @param {boolean} [unique=false] + * @return {Promise} + */ + ensureStringIndex(collection, path, unique) { + return ejdb2.ensureStringIndex(this._db, collection, path, unique).then(_ => this); + } + + /** + * Removes specified database index. + * + * @param {string} collection + * @param {string} path + * @param {boolean} [unique=false] + * @return {Promise} + */ + removeStringIndex(collection, path, unique) { + return ejdb2.removeStringIndex(this._db, collection, path, unique).then(_ => this); + } + + /** + * Ensures json document database index specified by [path] json pointer to integer data type. + * + * @param {string} collection + * @param {string} path + * @param {boolean} [unique=false] + * @return {Promise} + */ + ensureIntIndex(collection, path, unique) { + return ejdb2.ensureIntIndex(this._db, collection, path, unique).then(_ => this); + } + + /** + * Removes specified database index. + * + * @param {string} collection + * @param {string} path + * @param {boolean} [unique=false] + * @return {Promise} + */ + removeIntIndex(collection, path, unique) { + return ejdb2.removeIntIndex(this._db, collection, path, unique).then(_ => this); + } + + /** + * Ensures json document database index specified by [path] json pointer to floating point data type. + * + * @param {string} collection + * @param {string} path + * @param {boolean} [unique=false] + * @return {Promise} + */ + ensureFloatIndex(collection, path, unique) { + return ejdb2.ensureFloatIndex(this._db, collection, path, unique).then(_ => this); + } + + /** + * Removes specified database index. + * + * @param {string} collection + * @param {string} path + * @param {boolean} [unique=false] + * @return {Promise} + */ + removeFloatIndex(collection, path, unique) { + return ejdb2.removeFloatIndex(this._db, collection, path, unique).then(_ => this); + } + + /** + * Creates an online database backup image and copies it into the specified [fileName]. + * During online backup phase read/write database operations are allowed and not + * blocked for significant amount of time. Returns promise with backup + * finish time as number of milliseconds since epoch. + * + * @param {string} fileName Backup image file path. + * @returns {Promise} + */ + onlineBackup(fileName) { + return ejdb2.onlineBackup(this._db, fileName); + } +} + +module.exports = { + EJDB2, + JBE +}; diff --git a/src/bindings/ejdb2_react_native/binding/package.json b/src/bindings/ejdb2_react_native/binding/package.json new file mode 100644 index 0000000..5ab0e11 --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/package.json @@ -0,0 +1,37 @@ +{ + "name": "ejdb2_react_native", + "version": "@EJDB2_RN_VERSION@", + "repository": "https://github.com/Softmotions/ejdb.git", + "author": "Anton Adamansky ", + "description": "EJDB2 React Native binding", + "license": "MIT", + "main": "index.js", + "types": "index.d.ts", + "scripts": {}, + "keywords": [ + "react-native", + "ejdb", + "ejdb2", + "nosql", + "json", + "database", + "storage", + "embedded", + "embeddable", + "native", + "binding" + ], + "files": [ + "android/gradle/**", + "android/libs/**", + "android/src/**", + "android/build.gradle", + "android/gradlew*", + "index.d.ts", + "index.js", + "README.md" + ], + "peerDependencies": { + "react-native": "^0.61.3" + } +} diff --git a/src/bindings/ejdb2_react_native/binding/version.txt b/src/bindings/ejdb2_react_native/binding/version.txt new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/src/bindings/ejdb2_react_native/binding/version.txt @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/src/bindings/ejdb2_react_native/tests/.eslintrc.js b/src/bindings/ejdb2_react_native/tests/.eslintrc.js new file mode 100644 index 0000000..40c6dcd --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: '@react-native-community', +}; diff --git a/src/bindings/ejdb2_react_native/tests/.flowconfig b/src/bindings/ejdb2_react_native/tests/.flowconfig new file mode 100644 index 0000000..1319ea1 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/.flowconfig @@ -0,0 +1,99 @@ +[ignore] +; We fork some components by platform +.*/*[.]android.js + +; Ignore "BUCK" generated dirs +/\.buckd/ + +; Ignore unexpected extra "@providesModule" +.*/node_modules/.*/node_modules/fbjs/.* + +; Ignore duplicate module providers +; For RN Apps installed via npm, "Libraries" folder is inside +; "node_modules/react-native" but in the source repo it is in the root +node_modules/react-native/Libraries/react-native/React.js + +; Ignore polyfills +node_modules/react-native/Libraries/polyfills/.* + +; These should not be required directly +; require from fbjs/lib instead: require('fbjs/lib/warning') +node_modules/warning/.* + +; Flow doesn't support platforms +.*/Libraries/Utilities/HMRLoadingView.js + +[untyped] +.*/node_modules/@react-native-community/cli/.*/.* + +[include] + +[libs] +node_modules/react-native/Libraries/react-native/react-native-interface.js +node_modules/react-native/flow/ + +[options] +emoji=true + +esproposal.optional_chaining=enable +esproposal.nullish_coalescing=enable + +module.file_ext=.js +module.file_ext=.json +module.file_ext=.ios.js + +module.system=haste +module.system.haste.use_name_reducers=true +# get basename +module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1' +# strip .js or .js.flow suffix +module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1' +# strip .ios suffix +module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1' +module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' +module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' +module.system.haste.paths.blacklist=.*/__tests__/.* +module.system.haste.paths.blacklist=.*/__mocks__/.* +module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.* +module.system.haste.paths.whitelist=/node_modules/react-native/RNTester/.* +module.system.haste.paths.whitelist=/node_modules/react-native/IntegrationTests/.* +module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/react-native/react-native-implementation.js +module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* + +munge_underscores=true + +module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' + +suppress_type=$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FlowFixMeProps +suppress_type=$FlowFixMeState + +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError + +[lints] +sketchy-null-number=warn +sketchy-null-mixed=warn +sketchy-number=warn +untyped-type-import=warn +nonstrict-import=warn +deprecated-type=warn +unsafe-getters-setters=warn +inexact-spread=warn +unnecessary-invariant=warn +signature-verification-failure=warn +deprecated-utility=error + +[strict] +deprecated-type +nonstrict-import +sketchy-null +unclear-type +unsafe-getters-setters +untyped-import +untyped-type-import + +[version] +^0.98.0 diff --git a/src/bindings/ejdb2_react_native/tests/.gitattributes b/src/bindings/ejdb2_react_native/tests/.gitattributes new file mode 100644 index 0000000..d42ff18 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/.gitattributes @@ -0,0 +1 @@ +*.pbxproj -text diff --git a/src/bindings/ejdb2_react_native/tests/.gitignore b/src/bindings/ejdb2_react_native/tests/.gitignore new file mode 100644 index 0000000..828cc88 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/.gitignore @@ -0,0 +1,59 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml + +# node.js +# +node_modules/ +npm-debug.log +yarn-error.log + +# BUCK +buck-out/ +\.buckd/ +*.keystore + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +*/fastlane/report.xml +*/fastlane/Preview.html +*/fastlane/screenshots + +# Bundle artifact +*.jsbundle + +# CocoaPods +/ios/Pods/ diff --git a/src/bindings/ejdb2_react_native/tests/.watchmanconfig b/src/bindings/ejdb2_react_native/tests/.watchmanconfig new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/.watchmanconfig @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/bindings/ejdb2_react_native/tests/App.js b/src/bindings/ejdb2_react_native/tests/App.js new file mode 100644 index 0000000..d18a10a --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/App.js @@ -0,0 +1,304 @@ +/** + * Sample React Native App + * https://github.com/facebook/react-native + * + * @format + * @flow + */ + +import React, { Component } from 'react'; +import { Button, StyleSheet, Text, View } from 'react-native'; +import { EJDB2, JBE } from 'ejdb2_react_native'; + +const { assert } = require('chai'); +const t = { + true: a => assert.isTrue(a), + is: (a, b) => assert.equal(a, b), + throwsAsync: (a, b) => a.catch(e => assert.deepInclude(e, b)), + deepEqual: (a, b) => assert.deepEqual(a, b), +}; + +export default class App extends Component { + constructor(props) { + super(props); + this.state = { + status: 0, + } + } + + async test() { + + const db = await EJDB2.open('hello.db', { truncate: true }); + + await db.createQuery('@mycoll/*').use(q => { + t.true(q != null); + t.is(q.collection, 'mycoll'); + t.true(q.db != null); + }); + + let id = await db.put('mycoll', { 'foo': 'bar' }); + t.is(id, 1); + + await t.throwsAsync(db.put('mycoll', '{"'), { + code: '86005', + message: 'com.softmotions.ejdb2.EJDB2Exception: @ejdb IWRC:86005 errno:0 Unquoted JSON string (JBL_ERROR_PARSE_UNQUOTED_STRING)' + }); + + let doc = await db.get('mycoll', 1); + t.deepEqual(doc, { foo: 'bar' }); + + await db.put('mycoll', { 'foo': 'baz' }); + + const list = await db.createQuery('@mycoll/*').use(q => q.list({ limit: 1 })); + t.is(list.length, 1); + + let first = await db.createQuery('@mycoll/*').use(q => q.first()); + t.true(first != null); + t.deepEqual(first.json, { foo: 'baz' }); + t.is(first._raw, null); + + + first = await db.createQuery('@mycoll/[zzz=bbb]').use(q => q.first()); + t.is(first, undefined); + + let firstN = await db.createQuery('@mycoll/*').use(q => q.firstN(5)); + t.true(firstN != null && firstN.length == 2); + t.deepEqual(firstN[0].json, { foo: 'baz' }); + t.deepEqual(firstN[1].json, { foo: 'bar' }); + + firstN = await db.createQuery('@mycoll/*').use(q => q.firstN(1)); + t.true(firstN != null && firstN.length == 1); + t.deepEqual(firstN[0].json, { foo: 'baz' }); + + // Query 1 + const rbuf = []; + await db.createQuery('@mycoll/*').withLimit(10).useExecute((doc) => { + rbuf.push(doc.id); + rbuf.push(doc._raw); + }); + t.is(rbuf.toString(), '2,{"foo":"baz"},1,{"foo":"bar"}'); + + // Query 2 + rbuf.length = 0; + await db.createQuery('@mycoll/[foo=:? and foo=:bar]') + .setString(0, 'baz') + .setString('bar', 'baz') + .useExecute((doc) => { + rbuf.push(doc.id); + rbuf.push(doc._raw); + }); + t.is(rbuf.toString(), '2,{"foo":"baz"}'); + + + let error = null; + try { + await db.createQuery('@mycoll/[').useExecute(); + } catch (e) { + error = e; + t.true(JBE.isInvalidQuery(e)); + } + t.true(error != null); + error = null; + + let q = await db.createQuery('@mycoll/*'); + q.close(); + + try { + await q.useExecute(); + } catch (e) { + error = e; + t.true(e.toString().indexOf('IllegalStateException') != -1); + } + t.true(error != null); + error = null; + + let count = await db.createQuery('@mycoll/* | count').use(q => q.scalarInt()); + t.is(count, 2); + + await db.del('mycoll', 1); + + error = null; + try { + await db.get('mycoll', 1); + } catch (e) { + error = e; + t.true(JBE.isNotFound(e)); + } + t.true(error != null); + error = null; + + count = await db.createQuery('@mycoll/* | count').use(q => q.scalarInt()); + t.is(count, 1); + + // Patch + await db.patch('mycoll', '[{"op":"add", "path":"/baz", "value":"qux"}]', 2); + doc = await db.get('mycoll', 2); + t.deepEqual(doc, { foo: 'baz', baz: 'qux' }); + + // DB Info + doc = await db.info(); + assert.deepNestedInclude(doc, { + 'collections[0]': { + name: 'mycoll', + rnum: 1, + dbid: 3, + indexes: [] + } + }); + + // Indexes + await db.ensureStringIndex('mycoll', '/foo', true); + doc = await db.info(); + assert.deepNestedInclude(doc, { + 'collections[0].indexes[0]': { + ptr: '/foo', + mode: 5, + idbf: 0, + dbid: 4, + rnum: 1 + } + }); + + // Test JQL set + doc = await db.createQuery('@mycoll/[foo=:?]') + .setString(0, 'baz').use(q => q.first()); + assert.deepInclude(doc.json, { + foo: 'baz', + baz: 'qux' + }); + + // Test explain log + let log; + q = await db.createQuery('@mycoll/[foo=:?]') + .setString(0, 'baz') + .withExplain(); + await q.useExecute(); + log = q.explainLog; + t.true(log.indexOf('[INDEX] MATCHED UNIQUE|STR|1 /foo EXPR1: \'foo = :?\' INIT: IWKV_CURSOR_EQ') != -1); + + + await db.createQuery('@mycoll/[foo=:?]') + .setString(0, 'baz') + .useExecute((jbdoc, jql) => { + t.true(jql != null); + q.explainLog; + t.true(log.indexOf('[INDEX] MATCHED UNIQUE|STR|1 /foo EXPR1: \'foo = :?\' INIT: IWKV_CURSOR_EQ') != -1); + }); + + doc = await db.createQuery('@mycoll/[foo=:?] and /[baz=:?]') + .setString(0, 'baz') + .setString(1, 'qux') + .use(q => q.first()); + + assert.deepInclude(doc.json, { + foo: 'baz', + baz: 'qux' + }); + + + doc = await db.createQuery('@mycoll/[foo re :?]').setRegexp(0, '.*').use(q => q.first()); + assert.deepInclude(doc.json, { + foo: 'baz', + baz: 'qux' + }); + + doc = await db.createQuery('@mycoll/[foo re :?]').setRegexp(0, /.*/).use(q => q.first()); + assert.deepInclude(doc.json, { + foo: 'baz', + baz: 'qux' + }); + + doc = await db.createQuery('@mycoll/* | /foo').use(q => q.first()); + assert.deepEqual(doc.json, { foo: 'baz' }); + + await db.removeStringIndex('mycoll', '/foo', true); + doc = await db.info(); + + const dbfile = doc.file; + t.true(typeof dbfile == 'string'); + + assert.deepNestedInclude(doc, { + 'collections[0].indexes': [] + }); + + await db.removeCollection('mycoll'); + doc = await db.info(); + assert.deepNestedInclude(doc, { + 'collections': [] + }); + + q = db.createQuery('@c1/* | limit 2 skip 3'); + t.is(q.limit, 2); + t.is(q.skip, 3); + q.close(); + + // Rename collection + id = await db.put('cc1', { 'foo': 1 }); + t.true(id > 0); + doc = await db.get('cc1', id); + t.deepEqual(doc, { 'foo': 1 }); + + await db.renameCollection('cc1', 'cc2'); + doc = await db.get('cc2', id); + t.deepEqual(doc, { 'foo': 1 }); + + + let i = 0; + for (i = 0; i < 1023; ++i) { + await db.put('load', { 'name': `v${i}` }); + } + + i = 0; + await db.createQuery('@load/* | inverse').useExecute(((jbdoc) => { + const json = jbdoc.json; + t.is(json.name, `v${i}`); + i++; + })); + t.is(i, 1023); + + const ts0 = +new Date(); + const ts = await db.onlineBackup(`${dbfile}.bkp`); + t.true(ts0 < ts); + + await db.close(); + + // Restore from backup + const db2 = await EJDB2.open('hello.db.bkp', { truncate: false }); + doc = await db2.get('cc2', id); + t.deepEqual(doc, { 'foo': 1 }); + await db2.close(); + } + + render() { + return ( + + {this.state.status + ? {this.state.status} + : } + + ); + } + + async run() { + try { + this.setState({ status: 'Testing' }); + + await this.test(); + + this.setState({ status: 'OK' }); + } catch (e) { + console.log('Error ', e); + this.setState({ status: `${e.code || ''} ${e}` }); + throw e; + } + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F5FCFF', + }, +}); diff --git a/src/bindings/ejdb2_react_native/tests/CMakeLists.txt b/src/bindings/ejdb2_react_native/tests/CMakeLists.txt new file mode 100644 index 0000000..d7f9bfc --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/CMakeLists.txt @@ -0,0 +1,19 @@ +include(CTest) + +add_custom_command( + OUTPUT node_modules + COMMAND ${YARN_EXEC} install + DEPENDS ejdb2_react_native + ${CMAKE_CURRENT_SOURCE_DIR}/package.json + VERBATIM +) + +add_custom_target( + ejdb2_react_native_tests ALL + COMMAND ${YARN_EXEC} build-dev + DEPENDS node_modules +) + +add_test(NAME ejdb2_react_native + COMMAND ${YARN_EXEC} test-dev +) \ No newline at end of file diff --git a/src/bindings/ejdb2_react_native/tests/android/.gitignore b/src/bindings/ejdb2_react_native/tests/android/.gitignore new file mode 100644 index 0000000..bd67b9f --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/.gitignore @@ -0,0 +1 @@ +!/gradle/wrapper/gradle-wrapper.jar diff --git a/src/bindings/ejdb2_react_native/tests/android/.npmignore b/src/bindings/ejdb2_react_native/tests/android/.npmignore new file mode 100644 index 0000000..25edae2 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/.npmignore @@ -0,0 +1 @@ +/gradle/wrapper/gradle-wrapper.jar diff --git a/src/bindings/ejdb2_react_native/tests/android/app/build.gradle b/src/bindings/ejdb2_react_native/tests/android/app/build.gradle new file mode 100644 index 0000000..4791e43 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/app/build.gradle @@ -0,0 +1,181 @@ +apply plugin: "com.android.application" + +import com.android.build.OutputFile + +/** + * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets + * and bundleReleaseJsAndAssets). + * These basically call `react-native bundle` with the correct arguments during the Android build + * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the + * bundle directly from the development server. Below you can see all the possible configurations + * and their defaults. If you decide to add a configuration block, make sure to add it before the + * `apply from: "../../node_modules/react-native/react.gradle"` line. + * + * project.ext.react = [ + * // the name of the generated asset file containing your JS bundle + * bundleAssetName: "index.android.bundle", + * + * // the entry file for bundle generation + * entryFile: "index.android.js", + * + * // whether to bundle JS and assets in debug mode + * bundleInDebug: false, + * + * // whether to bundle JS and assets in release mode + * bundleInRelease: true, + * + * // whether to bundle JS and assets in another build variant (if configured). + * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants + * // The configuration property can be in the following formats + * // 'bundleIn${productFlavor}${buildType}' + * // 'bundleIn${buildType}' + * // bundleInFreeDebug: true, + * // bundleInPaidRelease: true, + * // bundleInBeta: true, + * + * // whether to disable dev mode in custom build variants (by default only disabled in release) + * // for example: to disable dev mode in the staging build type (if configured) + * devDisabledInStaging: true, + * // The configuration property can be in the following formats + * // 'devDisabledIn${productFlavor}${buildType}' + * // 'devDisabledIn${buildType}' + * + * // the root of your project, i.e. where "package.json" lives + * root: "../../", + * + * // where to put the JS bundle asset in debug mode + * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", + * + * // where to put the JS bundle asset in release mode + * jsBundleDirRelease: "$buildDir/intermediates/assets/release", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in debug mode + * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in release mode + * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", + * + * // by default the gradle tasks are skipped if none of the JS files or assets change; this means + * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to + * // date; if you have any other folders that you want to ignore for performance reasons (gradle + * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ + * // for example, you might want to remove it from here. + * inputExcludes: ["android/**", "ios/**"], + * + * // override which node gets called and with what additional arguments + * nodeExecutableAndArgs: ["node"], + * + * // supply additional arguments to the packager + * extraPackagerArgs: [] + * ] + */ + +project.ext.react = [ + entryFile: "index.js", + // your index js if not default, other settings + // Hermes JSC ? + enableHermes: false, +] + +apply from: "../../node_modules/react-native/react.gradle" + +/** + * Set this to true to create two separate APKs instead of one: + * - An APK that only works on ARM devices + * - An APK that only works on x86 devices + * The advantage is the size of the APK is reduced by about 4MB. + * Upload all the APKs to the Play Store and people will download + * the correct one based on the CPU architecture of their device. + */ +def enableSeparateBuildPerCPUArchitecture = false + +/** + * Run Proguard to shrink the Java bytecode in release builds. + */ +def enableProguardInReleaseBuilds = false + + +/** + * Use international variant JavaScriptCore + * International variant includes ICU i18n library and necessary data allowing to use + * e.g. Date.toLocaleString and String.localeCompare that give correct results + * when using with locales other than en-US. + * Note that this variant is about 6MiB larger per architecture than default. + */ +def useIntlJsc = false + + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + applicationId "com.test" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.0" + + testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + } + splits { + abi { + reset() + enable enableSeparateBuildPerCPUArchitecture + universalApk false // If true, also generate a universal APK + include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" + } + } + buildTypes { + release { + minifyEnabled enableProguardInReleaseBuilds + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + } + } + // applicationVariants are e.g. debug, release + applicationVariants.all { variant -> + variant.outputs.each { output -> + // For each separate APK per architecture, set a unique version code as described here: + // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits + def versionCodes = ["armeabi-v7a":1, "x86":2, "arm64-v8a": 3, "x86_64": 4] + def abi = output.getFilter(OutputFile.ABI) + if (abi != null) { // null for the universal-debug, universal-release variants + output.versionCodeOverride = + versionCodes.get(abi) * 1048576 + defaultConfig.versionCode + } + } + } +} + +dependencies { + implementation project(':ejdb2_react_native') + implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation "androidx.annotation:annotation:1.1.0" + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation "com.facebook.react:react-native:+" // From node_modules + androidTestImplementation('com.wix:detox:+') { transitive = true } + androidTestImplementation 'junit:junit:4.12' + + // JSC from node_modules + if (useIntlJsc) { + implementation 'org.webkit:android-jsc-intl:+' + } else { + implementation 'org.webkit:android-jsc:+' + } +} + + +// Run this once to be able to run the application with BUCK +// puts all compile dependencies into folder libs for BUCK to use +task copyDownloadableDepsToLibs(type: Copy) { + from configurations.compile + into 'libs' +} diff --git a/src/bindings/ejdb2_react_native/tests/android/app/proguard-rules.pro b/src/bindings/ejdb2_react_native/tests/android/app/proguard-rules.pro new file mode 100644 index 0000000..a92fa17 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/androidTest/java/com/test/DetoxTest.java b/src/bindings/ejdb2_react_native/tests/android/app/src/androidTest/java/com/test/DetoxTest.java new file mode 100644 index 0000000..337deb8 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/app/src/androidTest/java/com/test/DetoxTest.java @@ -0,0 +1,24 @@ +package com.test; + +import com.wix.detox.Detox; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; +import androidx.test.rule.ActivityTestRule; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class DetoxTest { + + @Rule + public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false); + + @Test + public void runDetoxTests() { + Detox.runTests(mActivityRule); + } +} \ No newline at end of file diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/debug/AndroidManifest.xml b/src/bindings/ejdb2_react_native/tests/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..185e01c --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/main/AndroidManifest.xml b/src/bindings/ejdb2_react_native/tests/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..74c7021 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/main/java/com/test/MainActivity.java b/src/bindings/ejdb2_react_native/tests/android/app/src/main/java/com/test/MainActivity.java new file mode 100644 index 0000000..a22ba0b --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/app/src/main/java/com/test/MainActivity.java @@ -0,0 +1,15 @@ +package com.test; + +import com.facebook.react.ReactActivity; + +public class MainActivity extends ReactActivity { + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "test"; + } +} diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/main/java/com/test/MainApplication.java b/src/bindings/ejdb2_react_native/tests/android/app/src/main/java/com/test/MainApplication.java new file mode 100644 index 0000000..0240f2a --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/app/src/main/java/com/test/MainApplication.java @@ -0,0 +1,47 @@ +package com.test; + +import android.app.Application; + +import com.facebook.react.ReactApplication; +import com.softmotions.ejdb2.EJDB2Package; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; +import com.facebook.soloader.SoLoader; + +import java.util.Arrays; +import java.util.List; + +public class MainApplication extends Application implements ReactApplication { + + private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { + @Override + public boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage(), + new EJDB2Package() + ); + } + + @Override + protected String getJSMainModuleName() { + return "index"; + } + }; + + @Override + public ReactNativeHost getReactNativeHost() { + return mReactNativeHost; + } + + @Override + public void onCreate() { + super.onCreate(); + SoLoader.init(this, /* native exopackage */ false); + } +} diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a2f5908 Binary files /dev/null and b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..1b52399 Binary files /dev/null and b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..ff10afd Binary files /dev/null and b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..115a4c7 Binary files /dev/null and b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..dcd3cd8 Binary files /dev/null and b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..459ca60 Binary files /dev/null and b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..8ca12fe Binary files /dev/null and b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..8e19b41 Binary files /dev/null and b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..b824ebd Binary files /dev/null and b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..4c19a13 Binary files /dev/null and b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/values/strings.xml b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..15043e8 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + test + diff --git a/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/values/styles.xml b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..319eb0c --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/src/bindings/ejdb2_react_native/tests/android/build.gradle b/src/bindings/ejdb2_react_native/tests/android/build.gradle new file mode 100644 index 0000000..81702e8 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/build.gradle @@ -0,0 +1,43 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext { + buildToolsVersion = "28.0.3" + supportLibVersion = "28.0.0" + compileSdkVersion = 28 + targetSdkVersion = 28 + minSdkVersion = 21 + kotlinVersion = '1.3.0' + detoxKotlinVersion = kotlinVersion + } + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.3.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "$rootDir/../node_modules/react-native/android" + } + maven { + url("$rootDir/../node_modules/jsc-android/dist") + } + maven { + // All of Detox' artifacts are provided via the npm module + url "$rootDir/../node_modules/detox/Detox-android" + } + mavenLocal() + google() + jcenter() + } +} diff --git a/src/bindings/ejdb2_react_native/tests/android/gradle.properties b/src/bindings/ejdb2_react_native/tests/android/gradle.properties new file mode 100644 index 0000000..223c6a0 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/src/bindings/ejdb2_react_native/tests/android/gradle/wrapper/gradle-wrapper.jar b/src/bindings/ejdb2_react_native/tests/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5c2d1cf Binary files /dev/null and b/src/bindings/ejdb2_react_native/tests/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src/bindings/ejdb2_react_native/tests/android/gradle/wrapper/gradle-wrapper.properties b/src/bindings/ejdb2_react_native/tests/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..4b7e1f3 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/src/bindings/ejdb2_react_native/tests/android/gradlew b/src/bindings/ejdb2_react_native/tests/android/gradlew new file mode 100755 index 0000000..8e25e6c --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/gradlew @@ -0,0 +1,188 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/src/bindings/ejdb2_react_native/tests/android/gradlew.bat b/src/bindings/ejdb2_react_native/tests/android/gradlew.bat new file mode 100644 index 0000000..9618d8d --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/bindings/ejdb2_react_native/tests/android/keystores/debug.keystore.properties b/src/bindings/ejdb2_react_native/tests/android/keystores/debug.keystore.properties new file mode 100644 index 0000000..121bfb4 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/keystores/debug.keystore.properties @@ -0,0 +1,4 @@ +key.store=debug.keystore +key.alias=androiddebugkey +key.store.password=android +key.alias.password=android diff --git a/src/bindings/ejdb2_react_native/tests/android/settings.gradle b/src/bindings/ejdb2_react_native/tests/android/settings.gradle new file mode 100644 index 0000000..7eeb523 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/android/settings.gradle @@ -0,0 +1,5 @@ +rootProject.name = 'test' +include ':ejdb2_react_native' +project(':ejdb2_react_native').projectDir = new File(rootProject.projectDir, '../node_modules/ejdb2_react_native/android') + +include ':app' diff --git a/src/bindings/ejdb2_react_native/tests/app.json b/src/bindings/ejdb2_react_native/tests/app.json new file mode 100644 index 0000000..f3c9ee1 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/app.json @@ -0,0 +1,4 @@ +{ + "name": "test", + "displayName": "test" +} \ No newline at end of file diff --git a/src/bindings/ejdb2_react_native/tests/babel.config.js b/src/bindings/ejdb2_react_native/tests/babel.config.js new file mode 100644 index 0000000..f842b77 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: ['module:metro-react-native-babel-preset'], +}; diff --git a/src/bindings/ejdb2_react_native/tests/e2e/firstTest.spec.js b/src/bindings/ejdb2_react_native/tests/e2e/firstTest.spec.js new file mode 100644 index 0000000..303d5c9 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/e2e/firstTest.spec.js @@ -0,0 +1,13 @@ +describe('EJDB2', () => { + + beforeEach(async () => { + await device.reloadReactNative(); + }); + + it('all', async () => { + await new Promise((resolve) => setTimeout(resolve, 2000)); + await element(by.id('run')).tap(); + await waitFor(element(by.id('status'))).toExist().withTimeout(2000); + await waitFor(element(by.id('status'))).toHaveText('OK').withTimeout(20000); + }); +}); diff --git a/src/bindings/ejdb2_react_native/tests/e2e/init.js b/src/bindings/ejdb2_react_native/tests/e2e/init.js new file mode 100644 index 0000000..b19d387 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/e2e/init.js @@ -0,0 +1,28 @@ +const detox = require('detox'); +const adapter = require('detox/runners/mocha/adapter'); +const execFile = require('child_process').execFile; +let config = require('../package.json').detox; + +if (process.env['ANDROID_AVD'] != null) { + config = config.replace(/"name":\s?"TestingAVD"/, `"name": ${process.env['ANDROID_AVD']}`) +} + +let server; + +before(async () => { + server = execFile('yarn', ['run', 'start'], {}); + await detox.init(config); +}); + +beforeEach(async function () { + await adapter.beforeEach(this); +}); + +afterEach(async function () { + await adapter.afterEach(this); +}); + +after(async () => { + await detox.cleanup(); + execFile('yarn', ['run', 'stop'], {}); +}); diff --git a/src/bindings/ejdb2_react_native/tests/e2e/mocha.opts b/src/bindings/ejdb2_react_native/tests/e2e/mocha.opts new file mode 100644 index 0000000..b82090c --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/e2e/mocha.opts @@ -0,0 +1 @@ +--recursive --timeout 120000 --bail --file e2e/init.js diff --git a/src/bindings/ejdb2_react_native/tests/index.js b/src/bindings/ejdb2_react_native/tests/index.js new file mode 100644 index 0000000..a850d03 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/index.js @@ -0,0 +1,9 @@ +/** + * @format + */ + +import {AppRegistry} from 'react-native'; +import App from './App'; +import {name as appName} from './app.json'; + +AppRegistry.registerComponent(appName, () => App); diff --git a/src/bindings/ejdb2_react_native/tests/metro.config.js b/src/bindings/ejdb2_react_native/tests/metro.config.js new file mode 100644 index 0000000..e476374 --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/metro.config.js @@ -0,0 +1,40 @@ +/** + * Metro configuration for React Native + * https://github.com/facebook/react-native + * + * @format + */ + +const path = require('path'); + +module.exports = { + transformer: { + getTransformOptions: async () => ({ + transform: { + experimentalImportSupport: false, + inlineRequires: false, + }, + }), + }, + resolver: { + extraNodeModules: new Proxy( + { + 'ejdb2_react_native': path.resolve(__dirname, '../binding') + }, + { + get: (target, name) => { + if (target.hasOwnProperty(name)) { + return target[name]; + } else { + return path.join(process.cwd(), `node_modules/${name}`); + } + } + } + ) + }, + projectRoot: path.resolve(__dirname), + watchFolders: [ + path.resolve(__dirname, '../binding') + ] +}; + diff --git a/src/bindings/ejdb2_react_native/tests/package.json b/src/bindings/ejdb2_react_native/tests/package.json new file mode 100644 index 0000000..3861d3e --- /dev/null +++ b/src/bindings/ejdb2_react_native/tests/package.json @@ -0,0 +1,43 @@ +{ + "name": "test", + "version": "1.0.0", + "private": true, + "scripts": { + "start": "react-native start", + "stop": "react-native-kill-packager", + "clean": "(cd ../binding && yarn unlink); rm -rf ./android/build", + "build": "yarn && detox build", + "test": "yarn run build && detox test -u", + "build-dev": "(cd ../binding && yarn link) && yarn && yarn link ejdb2_react_native && detox build", + "test-dev": "yarn run build-dev && detox test" + }, + "dependencies": { + "chai": "^4.2.0", + "react": "^16.11.0", + "react-native": "^0.61.3" + }, + "devDependencies": { + "@babel/core": "^7.6.4", + "@babel/runtime": "^7.6.3", + "babel-jest": "^24.9.0", + "detox": "^14.5.1", + "metro-react-native-babel-preset": "^0.56.3", + "mocha": "^6.2.2", + "react-native-kill-packager": "^1.0.0", + "react-test-renderer": "^16.11.0" + }, + "jest": { + "preset": "react-native" + }, + "detox": { + "test-runner": "mocha", + "configurations": { + "android.emu.debug": { + "binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk", + "build": "cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..", + "type": "android.emulator", + "name": "TestingAVD" + } + } + } +} diff --git a/src/bindings/ejdb2_swift/CMakeLists.txt b/src/bindings/ejdb2_swift/CMakeLists.txt new file mode 100644 index 0000000..052ed1c --- /dev/null +++ b/src/bindings/ejdb2_swift/CMakeLists.txt @@ -0,0 +1,67 @@ + +find_program(SWIFT_EXEC swift) +if (SWIFT_EXEC MATCHES "SWIFT_EXEC-NOTFOUND") + message(FATAL_ERROR "`swift` executable not found") +endif () + +file( + COPY . + DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + REGEX "(/\\.build)|(.+\\.db)" EXCLUDE +) + +list(TRANSFORM PROJECT_INCLUDE_DIRS PREPEND "-Xcc;-I" OUTPUT_VARIABLE SWIFT_IDIRS) + +if (CMAKE_BUILD_TYPE STREQUAL "Release") + set(SWIFT_BUILD_TYPE "release") +else() + set(SWIFT_BUILD_TYPE "debug") +endif() + +add_custom_target( + ejdb2_swift_binding ALL + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/EJDB2Swift + COMMAND swift build + -c ${SWIFT_BUILD_TYPE} + -Xlinker -L${CMAKE_BINARY_DIR}/src + -Xcc -DINPROJECT_BUILD + ${SWIFT_IDIRS} + COMMAND_EXPAND_LISTS +) + +add_dependencies(ejdb2_swift_binding ejdb2) + +if (BUILD_TESTS) + + if(${CMAKE_VERSION} VERSION_LESS "3.16.0") + + add_test( + NAME ejdb2_swift + COMMAND ${CMAKE_COMMAND} -E env "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/src;DYLD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/src" + swift test + -Xlinker -L${CMAKE_BINARY_DIR}/src + -Xcc -DINPROJECT_BUILD + ${SWIFT_IDIRS} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/EJDB2Swift + ) + + else() + + add_test( + NAME ejdb2_swift + COMMAND ${CMAKE_COMMAND} -E env "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/src;DYLD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/src" + swift test + -Xlinker -L${CMAKE_BINARY_DIR}/src + -Xcc -DINPROJECT_BUILD + ${SWIFT_IDIRS} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/EJDB2Swift + COMMAND_EXPAND_LISTS + ) + endif() + +endif() + + + + + diff --git a/src/ejdb2.c b/src/ejdb2.c new file mode 100644 index 0000000..ab56cad --- /dev/null +++ b/src/ejdb2.c @@ -0,0 +1,1955 @@ +#include "ejdb2_internal.h" + +// --------------------------------------------------------------------------- + +static iwrc _jb_put_new_lw(JBCOLL jbc, JBL jbl, int64_t *id); + +static const IWKV_val EMPTY_VAL = { 0 }; + +IW_INLINE iwrc _jb_meta_nrecs_removedb(EJDB db, uint32_t dbid) { + dbid = IW_HTOIL(dbid); + IWKV_val key = { + .size = sizeof(dbid), + .data = &dbid + }; + return iwkv_del(db->nrecdb, &key, 0); +} + +IW_INLINE iwrc _jb_meta_nrecs_update(EJDB db, uint32_t dbid, int64_t delta) { + delta = IW_HTOILL(delta); + dbid = IW_HTOIL(dbid); + IWKV_val val = { + .size = sizeof(delta), + .data = &delta + }; + IWKV_val key = { + .size = sizeof(dbid), + .data = &dbid + }; + return iwkv_put(db->nrecdb, &key, &val, IWKV_VAL_INCREMENT); +} + +static int64_t _jb_meta_nrecs_get(EJDB db, uint32_t dbid) { + size_t vsz = 0; + uint64_t ret = 0; + dbid = IW_HTOIL(dbid); + IWKV_val key = { + .size = sizeof(dbid), + .data = &dbid + }; + iwkv_get_copy(db->nrecdb, &key, &ret, sizeof(ret), &vsz); + if (vsz == sizeof(ret)) { + ret = IW_ITOHLL(ret); + } + return (int64_t) ret; +} + +static void _jb_idx_release(JBIDX idx) { + if (idx->idb) { + iwkv_db_cache_release(idx->idb); + } + free(idx->ptr); + free(idx); +} + +static void _jb_coll_release(JBCOLL jbc) { + if (jbc->cdb) { + iwkv_db_cache_release(jbc->cdb); + } + if (jbc->meta) { + jbl_destroy(&jbc->meta); + } + JBIDX nidx; + for (JBIDX idx = jbc->idx; idx; idx = nidx) { + nidx = idx->next; + _jb_idx_release(idx); + } + jbc->idx = 0; + pthread_rwlock_destroy(&jbc->rwl); + free(jbc); +} + +static iwrc _jb_coll_load_index_lr(JBCOLL jbc, IWKV_val *mval) { + binn *bn; + char *ptr; + struct _JBL imeta; + JBIDX idx = calloc(1, sizeof(*idx)); + if (!idx) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + + iwrc rc = jbl_from_buf_keep_onstack(&imeta, mval->data, mval->size); + RCGO(rc, finish); + bn = &imeta.bn; + + if ( !binn_object_get_str(bn, "ptr", &ptr) + || !binn_object_get_uint8(bn, "mode", &idx->mode) + || !binn_object_get_uint8(bn, "idbf", &idx->idbf) + || !binn_object_get_uint32(bn, "dbid", &idx->dbid)) { + rc = EJDB_ERROR_INVALID_COLLECTION_INDEX_META; + goto finish; + } + rc = jbl_ptr_alloc(ptr, &idx->ptr); + RCGO(rc, finish); + + rc = iwkv_db(jbc->db->iwkv, idx->dbid, idx->idbf, &idx->idb); + RCGO(rc, finish); + idx->jbc = jbc; + idx->rnum = _jb_meta_nrecs_get(jbc->db, idx->dbid); + idx->next = jbc->idx; + jbc->idx = idx; + +finish: + if (rc) { + _jb_idx_release(idx); + } + return rc; +} + +static iwrc _jb_coll_load_indexes_lr(JBCOLL jbc) { + iwrc rc = 0; + IWKV_cursor cur; + IWKV_val kval; + char buf[sizeof(KEY_PREFIX_IDXMETA) + JBNUMBUF_SIZE]; + // Full key format: i.. + int sz = snprintf(buf, sizeof(buf), KEY_PREFIX_IDXMETA "%u.", jbc->dbid); + if (sz >= sizeof(buf)) { + return IW_ERROR_OVERFLOW; + } + kval.data = buf; + kval.size = sz; + rc = iwkv_cursor_open(jbc->db->metadb, &cur, IWKV_CURSOR_GE, &kval); + if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + goto finish; + } + RCRET(rc); + + do { + IWKV_val key, val; + rc = iwkv_cursor_key(cur, &key); + RCGO(rc, finish); + if ((key.size > sz) && !strncmp(buf, key.data, sz)) { + iwkv_val_dispose(&key); + rc = iwkv_cursor_val(cur, &val); + RCGO(rc, finish); + rc = _jb_coll_load_index_lr(jbc, &val); + iwkv_val_dispose(&val); + RCBREAK(rc); + } else { + iwkv_val_dispose(&key); + } + } while (!(rc = iwkv_cursor_to(cur, IWKV_CURSOR_PREV))); + if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + } + +finish: + iwkv_cursor_close(&cur); + return rc; +} + +static iwrc _jb_coll_load_meta_lr(JBCOLL jbc) { + JBL jbv; + IWKV_cursor cur; + JBL jbm = jbc->meta; + iwrc rc = jbl_at(jbm, "/name", &jbv); + RCRET(rc); + jbc->name = jbl_get_str(jbv); + jbl_destroy(&jbv); + if (!jbc->name) { + return EJDB_ERROR_INVALID_COLLECTION_META; + } + rc = jbl_at(jbm, "/id", &jbv); + RCRET(rc); + jbc->dbid = (uint32_t) jbl_get_i64(jbv); + jbl_destroy(&jbv); + if (!jbc->dbid) { + return EJDB_ERROR_INVALID_COLLECTION_META; + } + rc = iwkv_db(jbc->db->iwkv, jbc->dbid, IWDB_VNUM64_KEYS, &jbc->cdb); + RCRET(rc); + + jbc->rnum = _jb_meta_nrecs_get(jbc->db, jbc->dbid); + + rc = _jb_coll_load_indexes_lr(jbc); + RCRET(rc); + + rc = iwkv_cursor_open(jbc->cdb, &cur, IWKV_CURSOR_BEFORE_FIRST, 0); + RCRET(rc); + rc = iwkv_cursor_to(cur, IWKV_CURSOR_NEXT); + if (rc) { + if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + } + } else { + size_t sz; + rc = iwkv_cursor_copy_key(cur, &jbc->id_seq, sizeof(jbc->id_seq), &sz, 0); + RCGO(rc, finish); + } + +finish: + iwkv_cursor_close(&cur); + return rc; +} + +static iwrc _jb_coll_init(JBCOLL jbc, IWKV_val *meta) { + int rci; + iwrc rc = 0; + + pthread_rwlockattr_t attr; + pthread_rwlockattr_init(&attr); +#if defined __linux__ && (defined __USE_UNIX98 || defined __USE_XOPEN2K) + pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); +#endif + pthread_rwlock_init(&jbc->rwl, &attr); + if (meta) { + rc = jbl_from_buf_keep(&jbc->meta, meta->data, meta->size, false); + RCRET(rc); + } + if (!jbc->meta) { + iwlog_error("Collection %s seems to be initialized", jbc->name); + return IW_ERROR_INVALID_STATE; + } + rc = _jb_coll_load_meta_lr(jbc); + RCRET(rc); + + khiter_t k = kh_put(JBCOLLM, jbc->db->mcolls, jbc->name, &rci); + if (rci != -1) { + kh_value(jbc->db->mcolls, k) = jbc; + } else { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + return rc; +} + +static iwrc _jb_idx_add_meta_lr(JBIDX idx, binn *list) { + iwrc rc = 0; + IWXSTR *xstr = iwxstr_new(); + if (!xstr) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + binn *meta = binn_object(); + if (!meta) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + iwxstr_destroy(xstr); + return rc; + } + rc = jbl_ptr_serialize(idx->ptr, xstr); + RCGO(rc, finish); + + if ( !binn_object_set_str(meta, "ptr", iwxstr_ptr(xstr)) + || !binn_object_set_uint32(meta, "mode", idx->mode) + || !binn_object_set_uint32(meta, "idbf", idx->idbf) + || !binn_object_set_uint32(meta, "dbid", idx->dbid) + || !binn_object_set_int64(meta, "rnum", idx->rnum)) { + rc = JBL_ERROR_CREATION; + } + + if (!binn_list_add_object(list, meta)) { + rc = JBL_ERROR_CREATION; + } + +finish: + iwxstr_destroy(xstr); + binn_free(meta); + return rc; +} + +static iwrc _jb_coll_add_meta_lr(JBCOLL jbc, binn *list) { + iwrc rc = 0; + binn *ilist = 0; + binn *meta = binn_object(); + if (!meta) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return rc; + } + if ( !binn_object_set_str(meta, "name", jbc->name) + || !binn_object_set_uint32(meta, "dbid", jbc->dbid) + || !binn_object_set_int64(meta, "rnum", jbc->rnum)) { + rc = JBL_ERROR_CREATION; + goto finish; + } + ilist = binn_list(); + if (!ilist) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + for (JBIDX idx = jbc->idx; idx; idx = idx->next) { + rc = _jb_idx_add_meta_lr(idx, ilist); + RCGO(rc, finish); + } + if (!binn_object_set_list(meta, "indexes", ilist)) { + rc = JBL_ERROR_CREATION; + goto finish; + } + if (!binn_list_add_value(list, meta)) { + rc = JBL_ERROR_CREATION; + goto finish; + } + +finish: + binn_free(meta); + if (ilist) { + binn_free(ilist); + } + return rc; +} + +static iwrc _jb_db_meta_load(EJDB db) { + iwrc rc = 0; + if (!db->metadb) { + rc = iwkv_db(db->iwkv, METADB_ID, 0, &db->metadb); + RCRET(rc); + } + if (!db->nrecdb) { + rc = iwkv_db(db->iwkv, NUMRECSDB_ID, IWDB_VNUM64_KEYS, &db->nrecdb); + RCRET(rc); + } + + IWKV_cursor cur; + rc = iwkv_cursor_open(db->metadb, &cur, IWKV_CURSOR_BEFORE_FIRST, 0); + RCRET(rc); + while (!(rc = iwkv_cursor_to(cur, IWKV_CURSOR_NEXT))) { + IWKV_val key, val; + rc = iwkv_cursor_get(cur, &key, &val); + RCGO(rc, finish); + if (!strncmp(key.data, KEY_PREFIX_COLLMETA, sizeof(KEY_PREFIX_COLLMETA) - 1)) { + JBCOLL jbc = calloc(1, sizeof(*jbc)); + if (!jbc) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + iwkv_val_dispose(&val); + goto finish; + } + jbc->db = db; + rc = _jb_coll_init(jbc, &val); + if (rc) { + _jb_coll_release(jbc); + iwkv_val_dispose(&key); + goto finish; + } + } else { + iwkv_val_dispose(&val); + } + iwkv_val_dispose(&key); + } + if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + } + +finish: + iwkv_cursor_close(&cur); + return rc; +} + +static iwrc _jb_db_release(EJDB *dbp) { + iwrc rc = 0; + EJDB db = *dbp; + *dbp = 0; +#ifdef JB_HTTP + if (db->jbr) { + IWRC(jbr_shutdown(&db->jbr), rc); + } +#endif + if (db->mcolls) { + for (khiter_t k = kh_begin(db->mcolls); k != kh_end(db->mcolls); ++k) { + if (!kh_exist(db->mcolls, k)) { + continue; + } + JBCOLL jbc = kh_val(db->mcolls, k); + _jb_coll_release(jbc); + } + kh_destroy(JBCOLLM, db->mcolls); + db->mcolls = 0; + } + if (db->iwkv) { + IWRC(iwkv_close(&db->iwkv), rc); + } + pthread_rwlock_destroy(&db->rwl); + + EJDB_HTTP *http = &db->opts.http; + if (http->bind) { + free((void*) http->bind); + } + if (http->access_token) { + free((void*) http->access_token); + } + free(db); + return rc; +} + +static iwrc _jb_coll_acquire_keeplock2(EJDB db, const char *coll, jb_coll_acquire_t acm, JBCOLL *jbcp) { + if (strlen(coll) > EJDB_COLLECTION_NAME_MAX_LEN) { + return EJDB_ERROR_INVALID_COLLECTION_NAME; + } + int rci; + iwrc rc = 0; + *jbcp = 0; + JBCOLL jbc = 0; + bool wl = acm & JB_COLL_ACQUIRE_WRITE; + API_RLOCK(db, rci); + khiter_t k = kh_get(JBCOLLM, db->mcolls, coll); + if (k != kh_end(db->mcolls)) { + jbc = kh_value(db->mcolls, k); + assert(jbc); + rci = wl ? pthread_rwlock_wrlock(&jbc->rwl) : pthread_rwlock_rdlock(&jbc->rwl); + if (rci) { + rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci); + goto finish; + } + *jbcp = jbc; + } else { + pthread_rwlock_unlock(&db->rwl); // relock + if ((db->oflags & IWKV_RDONLY) || (acm & JB_COLL_ACQUIRE_EXISTING)) { + return IW_ERROR_NOT_EXISTS; + } + API_WLOCK(db, rci); + k = kh_get(JBCOLLM, db->mcolls, coll); + if (k != kh_end(db->mcolls)) { + jbc = kh_value(db->mcolls, k); + assert(jbc); + rci = pthread_rwlock_rdlock(&jbc->rwl); + if (rci) { + rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci); + goto finish; + } + *jbcp = jbc; + } else { + JBL meta = 0; + IWDB cdb = 0; + uint32_t dbid = 0; + char keybuf[JBNUMBUF_SIZE + sizeof(KEY_PREFIX_COLLMETA)]; + IWKV_val key, val; + + rc = iwkv_new_db(db->iwkv, IWDB_VNUM64_KEYS, &dbid, &cdb); + RCGO(rc, create_finish); + jbc = calloc(1, sizeof(*jbc)); + if (!jbc) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto create_finish; + } + rc = jbl_create_empty_object(&meta); + RCGO(rc, create_finish); + if (!binn_object_set_str(&meta->bn, "name", coll)) { + rc = JBL_ERROR_CREATION; + goto create_finish; + } + if (!binn_object_set_uint32(&meta->bn, "id", dbid)) { + rc = JBL_ERROR_CREATION; + goto create_finish; + } + rc = jbl_as_buf(meta, &val.data, &val.size); + RCGO(rc, create_finish); + + key.size = snprintf(keybuf, sizeof(keybuf), KEY_PREFIX_COLLMETA "%u", dbid); + if (key.size >= sizeof(keybuf)) { + rc = IW_ERROR_OVERFLOW; + goto create_finish; + } + key.data = keybuf; + rc = iwkv_put(db->metadb, &key, &val, IWKV_SYNC); + RCGO(rc, create_finish); + + jbc->db = db; + jbc->meta = meta; + rc = _jb_coll_init(jbc, 0); + if (rc) { + iwkv_del(db->metadb, &key, IWKV_SYNC); + goto create_finish; + } + +create_finish: + if (rc) { + if (meta) { + jbl_destroy(&meta); + } + if (cdb) { + iwkv_db_destroy(&cdb); + } + if (jbc) { + jbc->meta = 0; // meta was cleared + _jb_coll_release(jbc); + } + } else { + rci = wl ? pthread_rwlock_wrlock(&jbc->rwl) : pthread_rwlock_rdlock(&jbc->rwl); // -V522 + if (rci) { + rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci); + goto finish; + } + *jbcp = jbc; + } + } + } + +finish: + if (rc) { + pthread_rwlock_unlock(&db->rwl); + } + return rc; +} + +IW_INLINE iwrc _jb_coll_acquire_keeplock(EJDB db, const char *coll, bool wl, JBCOLL *jbcp) { + return _jb_coll_acquire_keeplock2(db, coll, wl ? JB_COLL_ACQUIRE_WRITE : 0, jbcp); +} + +static iwrc _jb_idx_record_add(JBIDX idx, int64_t id, JBL jbl, JBL jblprev) { + IWKV_val key; + uint8_t step; + char vnbuf[IW_VNUMBUFSZ]; + char numbuf[JBNUMBUF_SIZE]; + + bool jbv_found, jbvprev_found; + struct _JBL jbv = { 0 }, jbvprev = { 0 }; + jbl_type_t jbv_type, jbvprev_type; + + iwrc rc = 0; + IWPOOL *pool = 0; + int64_t delta = 0; // delta of added/removed index records + bool compound = idx->idbf & IWDB_COMPOUND_KEYS; + + jbvprev_found = jblprev ? _jbl_at(jblprev, idx->ptr, &jbvprev) : false; + jbv_found = jbl ? _jbl_at(jbl, idx->ptr, &jbv) : false; + + jbv_type = jbl_type(&jbv); + jbvprev_type = jbl_type(&jbvprev); + + // Do not index NULLs, OBJECTs, ARRAYs (in `EJDB_IDX_UNIQUE` mode) + if ( ((jbvprev_type == JBV_OBJECT) || (jbvprev_type <= JBV_NULL)) + || ((jbvprev_type == JBV_ARRAY) && !compound)) { + jbvprev_found = false; + } + if ( ((jbv_type == JBV_OBJECT) || (jbv_type <= JBV_NULL)) + || ((jbv_type == JBV_ARRAY) && !compound)) { + jbv_found = false; + } + + if ( compound + && (jbv_type == jbvprev_type) + && (jbvprev_type == JBV_ARRAY)) { // compare next/prev obj arrays + pool = iwpool_create(1024); + if (!pool) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + JBL_NODE jbvprev_node, jbv_node; + rc = jbl_to_node(&jbv, &jbv_node, false, pool); + RCGO(rc, finish); + jbv.node = jbv_node; + + rc = jbl_to_node(&jbvprev, &jbvprev_node, false, pool); + RCGO(rc, finish); + jbvprev.node = jbvprev_node; + + if (_jbl_compare_nodes(jbv_node, jbvprev_node, &rc) == 0) { + goto finish; // Arrays are equal or error + } + } else if (_jbl_is_eq_atomic_values(&jbv, &jbvprev)) { + return 0; + } + + if (jbvprev_found) { // Remove old index elements + if (jbvprev_type == JBV_ARRAY) { // TODO: array modification delta? + JBL_NODE n; + if (!pool) { + pool = iwpool_create(1024); + if (!pool) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + RCGO(rc, finish); + } + } + rc = jbl_to_node(&jbvprev, &n, false, pool); + RCGO(rc, finish); + for (n = n->child; n; n = n->next) { + jbi_node_fill_ikey(idx, n, &key, numbuf); + if (key.size) { + key.compound = id; + rc = iwkv_del(idx->idb, &key, 0); + if (!rc) { + --delta; + } else if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + } + RCGO(rc, finish); + } + } + } else { + jbi_jbl_fill_ikey(idx, &jbvprev, &key, numbuf); + if (key.size) { + key.compound = id; + rc = iwkv_del(idx->idb, &key, 0); + if (!rc) { + --delta; + } else if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + } + RCGO(rc, finish); + } + } + } + + if (jbv_found) { // Add index record + if (jbv_type == JBV_ARRAY) { // TODO: array modification delta? + JBL_NODE n; + if (!pool) { + pool = iwpool_create(1024); + if (!pool) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + RCGO(rc, finish); + } + } + rc = jbl_to_node(&jbv, &n, false, pool); + RCGO(rc, finish); + for (n = n->child; n; n = n->next) { + jbi_node_fill_ikey(idx, n, &key, numbuf); + if (key.size) { + key.compound = id; + rc = iwkv_put(idx->idb, &key, &EMPTY_VAL, IWKV_NO_OVERWRITE); + if (!rc) { + ++delta; + } else if (rc == IWKV_ERROR_KEY_EXISTS) { + rc = 0; + } else { + goto finish; + } + } + } + } else { + jbi_jbl_fill_ikey(idx, &jbv, &key, numbuf); + if (key.size) { + if (compound) { + key.compound = id; + rc = iwkv_put(idx->idb, &key, &EMPTY_VAL, IWKV_NO_OVERWRITE); + if (!rc) { + ++delta; + } else if (rc == IWKV_ERROR_KEY_EXISTS) { + rc = 0; + } + } else { + IW_SETVNUMBUF64(step, vnbuf, id); + IWKV_val idval = { + .data = vnbuf, + .size = step + }; + rc = iwkv_put(idx->idb, &key, &idval, IWKV_NO_OVERWRITE); + if (!rc) { + ++delta; + } else if (rc == IWKV_ERROR_KEY_EXISTS) { + rc = EJDB_ERROR_UNIQUE_INDEX_CONSTRAINT_VIOLATED; + goto finish; + } + } + } + } + } + +finish: + if (pool) { + iwpool_destroy(pool); + } + if (delta && !_jb_meta_nrecs_update(idx->jbc->db, idx->dbid, delta)) { + idx->rnum += delta; + } + return rc; +} + +IW_INLINE iwrc _jb_idx_record_remove(JBIDX idx, int64_t id, JBL jbl) { + return _jb_idx_record_add(idx, id, 0, jbl); +} + +static iwrc _jb_idx_fill(JBIDX idx) { + IWKV_cursor cur; + IWKV_val key, val; + struct _JBL jbs; + int64_t llv; + JBL jbl = &jbs; + + iwrc rc = iwkv_cursor_open(idx->jbc->cdb, &cur, IWKV_CURSOR_BEFORE_FIRST, 0); + while (!rc) { + rc = iwkv_cursor_to(cur, IWKV_CURSOR_NEXT); + if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + break; + } + rc = iwkv_cursor_get(cur, &key, &val); + RCBREAK(rc); + if (!binn_load(val.data, &jbs.bn)) { + rc = JBL_ERROR_CREATION; + break; + } + memcpy(&llv, key.data, sizeof(llv)); + rc = _jb_idx_record_add(idx, llv, jbl, 0); + iwkv_kv_dispose(&key, &val); + } + IWRC(iwkv_cursor_close(&cur), rc); + return rc; +} + +// Used to avoid deadlocks within a `iwkv_put` context +static iwrc _jb_put_handler_after(iwrc rc, struct _JBPHCTX *ctx) { + IWKV_val *oldval = &ctx->oldval; + if (rc) { + if (oldval->size) { + iwkv_val_dispose(oldval); + } + return rc; + } + JBL prev; + struct _JBL jblprev; + JBCOLL jbc = ctx->jbc; + if (oldval->size) { + rc = jbl_from_buf_keep_onstack(&jblprev, oldval->data, oldval->size); + RCRET(rc); + prev = &jblprev; + } else { + prev = 0; + } + JBIDX fail_idx = 0; + for (JBIDX idx = jbc->idx; idx; idx = idx->next) { + rc = _jb_idx_record_add(idx, ctx->id, ctx->jbl, prev); + if (rc) { + fail_idx = idx; + goto finish; + } + } + if (!prev) { + _jb_meta_nrecs_update(jbc->db, jbc->dbid, 1); + jbc->rnum += 1; + } + +finish: + if (oldval->size) { + iwkv_val_dispose(oldval); + } + if (rc && !oldval->size) { + // Cleanup on error inserting new record + IWKV_val key = { .data = &ctx->id, .size = sizeof(ctx->id) }; + for (JBIDX idx = jbc->idx; idx && idx != fail_idx; idx = idx->next) { + IWRC(_jb_idx_record_remove(idx, ctx->id, ctx->jbl), rc); + } + IWRC(iwkv_del(jbc->cdb, &key, 0), rc); + } + return rc; +} + +static iwrc _jb_put_handler(const IWKV_val *key, const IWKV_val *val, IWKV_val *oldval, void *op) { + struct _JBPHCTX *ctx = op; + if (oldval && oldval->size) { + memcpy(&ctx->oldval, oldval, sizeof(*oldval)); + } + return 0; +} + +static iwrc _jb_exec_scan_init(JBEXEC *ctx) { + ctx->istep = 1; + ctx->jblbufsz = ctx->jbc->db->opts.document_buffer_sz; + ctx->jblbuf = malloc(ctx->jblbufsz); + if (!ctx->jblbuf) { + ctx->jblbufsz = 0; + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + struct JQP_AUX *aux = ctx->ux->q->aux; + if (aux->expr->flags & JQP_EXPR_NODE_FLAG_PK) { // Select by primary key + ctx->scanner = jbi_pk_scanner; + if (ctx->ux->log) { + iwxstr_cat2(ctx->ux->log, "[INDEX] PK"); + } + return 0; + } + iwrc rc = jbi_selection(ctx); + RCRET(rc); + if (ctx->midx.idx) { + if (ctx->midx.idx->idbf & IWDB_COMPOUND_KEYS) { + ctx->scanner = jbi_dup_scanner; + } else { + ctx->scanner = jbi_uniq_scanner; + } + } else { + ctx->scanner = jbi_full_scanner; + if (ctx->ux->log) { + iwxstr_cat2(ctx->ux->log, "[INDEX] NO"); + } + } + return 0; +} + +static void _jb_exec_scan_release(JBEXEC *ctx) { + if (ctx->proj_joined_nodes_cache) { + // Destroy projected nodes key + iwstree_destroy(ctx->proj_joined_nodes_cache); + } + if (ctx->proj_joined_nodes_pool) { + iwpool_destroy(ctx->proj_joined_nodes_pool); + } + free(ctx->jblbuf); +} + +static iwrc _jb_noop_visitor(struct _EJDB_EXEC *ctx, EJDB_DOC doc, int64_t *step) { + return 0; +} + +IW_INLINE iwrc _jb_put_impl(JBCOLL jbc, JBL jbl, int64_t id) { + IWKV_val val, key = { + .data = &id, + .size = sizeof(id) + }; + struct _JBPHCTX pctx = { + .id = id, + .jbc = jbc, + .jbl = jbl + }; + iwrc rc = jbl_as_buf(jbl, &val.data, &val.size); + RCRET(rc); + return _jb_put_handler_after(iwkv_puth(jbc->cdb, &key, &val, 0, _jb_put_handler, &pctx), &pctx); +} + +iwrc jb_put(JBCOLL jbc, JBL jbl, int64_t id) { + return _jb_put_impl(jbc, jbl, id); +} + +iwrc jb_cursor_set(JBCOLL jbc, IWKV_cursor cur, int64_t id, JBL jbl) { + IWKV_val val; + struct _JBPHCTX pctx = { + .id = id, + .jbc = jbc, + .jbl = jbl + }; + iwrc rc = jbl_as_buf(jbl, &val.data, &val.size); + RCRET(rc); + return _jb_put_handler_after(iwkv_cursor_seth(cur, &val, 0, _jb_put_handler, &pctx), &pctx); +} + +static iwrc _jb_exec_upsert_lw(JBEXEC *ctx) { + JBL_NODE n; + int64_t id; + iwrc rc = 0; + JBL jbl = 0; + EJDB_EXEC *ux = ctx->ux; + JQL q = ux->q; + if (q->aux->apply_placeholder) { + JQVAL *pv = jql_find_placeholder(q, q->aux->apply_placeholder); + if (!pv || (pv->type != JQVAL_JBLNODE) || !pv->vnode) { + rc = JQL_ERROR_INVALID_PLACEHOLDER_VALUE_TYPE; + goto finish; + } + n = pv->vnode; + } else { + n = q->aux->apply; + } + if (!n) { + // Skip silently, nothing to do. + goto finish; + } + RCC(rc, finish, jbl_from_node(&jbl, n)); + RCC(rc, finish, _jb_put_new_lw(ctx->jbc, jbl, &id)); + + if (!(q->aux->qmode & JQP_QRY_AGGREGATE)) { + struct _EJDB_DOC doc = { + .id = id, + .raw = jbl, + .node = n + }; + do { + ctx->istep = 1; + RCC(rc, finish, ux->visitor(ux, &doc, &ctx->istep)); + } while (ctx->istep == -1); + } + ++ux->cnt; + +finish: + jbl_destroy(&jbl); + return rc; +} + +//----------------------- Public API + +iwrc ejdb_exec(EJDB_EXEC *ux) { + if (!ux || !ux->db || !ux->q) { + return IW_ERROR_INVALID_ARGS; + } + int rci; + iwrc rc = 0; + if (!ux->visitor) { + ux->visitor = _jb_noop_visitor; + ux->q->aux->projection = 0; // Actually we don't need projection if exists + } + if (ux->log) { + // set terminating NULL to current pos of log + iwxstr_cat(ux->log, 0, 0); + } + JBEXEC ctx = { + .ux = ux + }; + if (ux->limit < 1) { + rc = jql_get_limit(ux->q, &ux->limit); + RCRET(rc); + if (ux->limit < 1) { + ux->limit = INT64_MAX; + } + } + if (ux->skip < 1) { + rc = jql_get_skip(ux->q, &ux->skip); + RCRET(rc); + } + rc = _jb_coll_acquire_keeplock2(ux->db, ux->q->coll, + jql_has_apply(ux->q) ? JB_COLL_ACQUIRE_WRITE : JB_COLL_ACQUIRE_EXISTING, + &ctx.jbc); + if (rc == IW_ERROR_NOT_EXISTS) { + return 0; + } else { + RCRET(rc); + } + + rc = _jb_exec_scan_init(&ctx); + RCGO(rc, finish); + if (ctx.sorting) { + if (ux->log) { + iwxstr_cat2(ux->log, " [COLLECTOR] SORTER\n"); + } + rc = ctx.scanner(&ctx, jbi_sorter_consumer); + } else { + if (ux->log) { + iwxstr_cat2(ux->log, " [COLLECTOR] PLAIN\n"); + } + rc = ctx.scanner(&ctx, jbi_consumer); + } + RCGO(rc, finish); + if ((ux->cnt == 0) && jql_has_apply_upsert(ux->q)) { + // No records found trying to upsert new record + rc = _jb_exec_upsert_lw(&ctx); + } + +finish: + _jb_exec_scan_release(&ctx); + API_COLL_UNLOCK(ctx.jbc, rci, rc); + jql_reset(ux->q, true, false); + return rc; +} + +struct JB_LIST_VISITOR_CTX { + EJDB_DOC head; + EJDB_DOC tail; +}; + +static iwrc _jb_exec_list_visitor(struct _EJDB_EXEC *ctx, EJDB_DOC doc, int64_t *step) { + struct JB_LIST_VISITOR_CTX *lvc = ctx->opaque; + IWPOOL *pool = ctx->pool; + struct _EJDB_DOC *ndoc = iwpool_alloc(sizeof(*ndoc) + sizeof(*doc->raw) + doc->raw->bn.size, pool); + if (!ndoc) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + ndoc->id = doc->id; + ndoc->raw = (void*) (((uint8_t*) ndoc) + sizeof(*ndoc)); + ndoc->raw->node = 0; + ndoc->node = doc->node; + ndoc->next = 0; + ndoc->prev = 0; + memcpy(&ndoc->raw->bn, &doc->raw->bn, sizeof(ndoc->raw->bn)); + ndoc->raw->bn.ptr = ((uint8_t*) ndoc) + sizeof(*ndoc) + sizeof(*doc->raw); + memcpy(ndoc->raw->bn.ptr, doc->raw->bn.ptr, doc->raw->bn.size); + + if (!lvc->head) { + lvc->head = ndoc; + lvc->tail = ndoc; + } else { + lvc->tail->next = ndoc; + ndoc->prev = lvc->tail; + lvc->tail = ndoc; + } + return 0; +} + +static iwrc _jb_list(EJDB db, JQL q, EJDB_DOC *first, int64_t limit, IWXSTR *log, IWPOOL *pool) { + if (!db || !q || !first || !pool) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + struct JB_LIST_VISITOR_CTX lvc = { 0 }; + struct _EJDB_EXEC ux = { + .db = db, + .q = q, + .visitor = _jb_exec_list_visitor, + .pool = pool, + .limit = limit, + .log = log, + .opaque = &lvc + }; + rc = ejdb_exec(&ux); + if (rc) { + *first = 0; + } else { + *first = lvc.head; + } + return rc; +} + +static iwrc _jb_count(EJDB db, JQL q, int64_t *count, int64_t limit, IWXSTR *log) { + if (!db || !q || !count) { + return IW_ERROR_INVALID_ARGS; + } + struct _EJDB_EXEC ux = { + .db = db, + .q = q, + .limit = limit, + .log = log + }; + iwrc rc = ejdb_exec(&ux); + *count = ux.cnt; + return rc; +} + +iwrc ejdb_count(EJDB db, JQL q, int64_t *count, int64_t limit) { + return _jb_count(db, q, count, limit, 0); +} + +iwrc ejdb_count2(EJDB db, const char *coll, const char *q, int64_t *count, int64_t limit) { + JQL jql; + iwrc rc = jql_create(&jql, coll, q); + RCRET(rc); + rc = _jb_count(db, jql, count, limit, 0); + jql_destroy(&jql); + return rc; +} + +iwrc ejdb_update(EJDB db, JQL q) { + int64_t count; + return ejdb_count(db, q, &count, 0); +} + +iwrc ejdb_update2(EJDB db, const char *coll, const char *q) { + int64_t count; + return ejdb_count2(db, coll, q, &count, 0); +} + +iwrc ejdb_list(EJDB db, JQL q, EJDB_DOC *first, int64_t limit, IWPOOL *pool) { + return _jb_list(db, q, first, limit, 0, pool); +} + +iwrc ejdb_list3(EJDB db, const char *coll, const char *query, int64_t limit, IWXSTR *log, EJDB_LIST *listp) { + if (!listp) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + *listp = 0; + IWPOOL *pool = iwpool_create(1024); + if (!pool) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + EJDB_LIST list = iwpool_alloc(sizeof(*list), pool); + if (!list) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + list->first = 0; + list->db = db; + list->pool = pool; + rc = jql_create(&list->q, coll, query); + RCGO(rc, finish); + rc = _jb_list(db, list->q, &list->first, limit, log, list->pool); + +finish: + if (rc) { + iwpool_destroy(pool); + } else { + *listp = list; + } + return rc; +} + +iwrc ejdb_list4(EJDB db, JQL q, int64_t limit, IWXSTR *log, EJDB_LIST *listp) { + if (!listp) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + *listp = 0; + IWPOOL *pool = iwpool_create(1024); + if (!pool) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + EJDB_LIST list = iwpool_alloc(sizeof(*list), pool); + if (!list) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + list->q = 0; + list->first = 0; + list->db = db; + list->pool = pool; + rc = _jb_list(db, q, &list->first, limit, log, list->pool); + +finish: + if (rc) { + iwpool_destroy(pool); + } else { + *listp = list; + } + return rc; +} + +iwrc ejdb_list2(EJDB db, const char *coll, const char *query, int64_t limit, EJDB_LIST *listp) { + return ejdb_list3(db, coll, query, limit, 0, listp); +} + +void ejdb_list_destroy(EJDB_LIST *listp) { + if (listp) { + EJDB_LIST list = *listp; + if (list) { + if (list->q) { + jql_destroy(&list->q); + } + if (list->pool) { + iwpool_destroy(list->pool); + } + } + *listp = 0; + } +} + +iwrc ejdb_remove_index(EJDB db, const char *coll, const char *path, ejdb_idx_mode_t mode) { + if (!db || !coll || !path) { + return IW_ERROR_INVALID_ARGS; + } + int rci; + JBCOLL jbc; + IWKV_val key; + JBL_PTR ptr = 0; + char keybuf[sizeof(KEY_PREFIX_IDXMETA) + 1 + 2 * JBNUMBUF_SIZE]; // Full key format: i.. + + iwrc rc = _jb_coll_acquire_keeplock2(db, coll, JB_COLL_ACQUIRE_WRITE | JB_COLL_ACQUIRE_EXISTING, &jbc); + RCRET(rc); + + rc = jbl_ptr_alloc(path, &ptr); + RCGO(rc, finish); + + for (JBIDX idx = jbc->idx, prev = 0; idx; idx = idx->next) { + if (((idx->mode & ~EJDB_IDX_UNIQUE) == (mode & ~EJDB_IDX_UNIQUE)) && !jbl_ptr_cmp(idx->ptr, ptr)) { + key.data = keybuf; + key.size = snprintf(keybuf, sizeof(keybuf), KEY_PREFIX_IDXMETA "%u" "." "%u", jbc->dbid, idx->dbid); + if (key.size >= sizeof(keybuf)) { + rc = IW_ERROR_OVERFLOW; + goto finish; + } + rc = iwkv_del(db->metadb, &key, 0); + RCGO(rc, finish); + _jb_meta_nrecs_removedb(db, idx->dbid); + if (prev) { + prev->next = idx->next; + } else { + jbc->idx = idx->next; + } + if (idx->idb) { + iwkv_db_destroy(&idx->idb); + } + _jb_idx_release(idx); + break; + } + prev = idx; + } + +finish: + free(ptr); + API_COLL_UNLOCK(jbc, rci, rc); + return rc; +} + +iwrc ejdb_ensure_index(EJDB db, const char *coll, const char *path, ejdb_idx_mode_t mode) { + if (!db || !coll || !path) { + return IW_ERROR_INVALID_ARGS; + } + int rci; + JBCOLL jbc; + IWKV_val key, val; + char keybuf[sizeof(KEY_PREFIX_IDXMETA) + 1 + 2 * JBNUMBUF_SIZE]; // Full key format: i.. + + JBIDX idx = 0; + JBL_PTR ptr = 0; + binn *imeta = 0; + + switch (mode & (EJDB_IDX_STR | EJDB_IDX_I64 | EJDB_IDX_F64)) { + case EJDB_IDX_STR: + case EJDB_IDX_I64: + case EJDB_IDX_F64: + break; + default: + return EJDB_ERROR_INVALID_INDEX_MODE; + } + + iwrc rc = _jb_coll_acquire_keeplock(db, coll, true, &jbc); + RCRET(rc); + rc = jbl_ptr_alloc(path, &ptr); + RCGO(rc, finish); + + for (idx = jbc->idx; idx; idx = idx->next) { + if (((idx->mode & ~EJDB_IDX_UNIQUE) == (mode & ~EJDB_IDX_UNIQUE)) && !jbl_ptr_cmp(idx->ptr, ptr)) { + if (idx->mode != mode) { + rc = EJDB_ERROR_MISMATCHED_INDEX_UNIQUENESS_MODE; + idx = 0; + } + goto finish; + } + } + + idx = calloc(1, sizeof(*idx)); + if (!idx) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + idx->mode = mode; + idx->jbc = jbc; + idx->ptr = ptr; + ptr = 0; + idx->idbf = 0; + if (mode & EJDB_IDX_I64) { + idx->idbf |= IWDB_VNUM64_KEYS; + } else if (mode & EJDB_IDX_F64) { + idx->idbf |= IWDB_REALNUM_KEYS; + } + if (!(mode & EJDB_IDX_UNIQUE)) { + idx->idbf |= IWDB_COMPOUND_KEYS; + } + rc = iwkv_new_db(db->iwkv, idx->idbf, &idx->dbid, &idx->idb); + RCGO(rc, finish); + + rc = _jb_idx_fill(idx); + RCGO(rc, finish); + + // save index meta into metadb + imeta = binn_object(); + if (!imeta) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + + if ( !binn_object_set_str(imeta, "ptr", path) + || !binn_object_set_uint32(imeta, "mode", idx->mode) + || !binn_object_set_uint32(imeta, "idbf", idx->idbf) + || !binn_object_set_uint32(imeta, "dbid", idx->dbid)) { + rc = JBL_ERROR_CREATION; + goto finish; + } + + key.data = keybuf; + // Full key format: i.. + key.size = snprintf(keybuf, sizeof(keybuf), KEY_PREFIX_IDXMETA "%u" "." "%u", jbc->dbid, idx->dbid); + if (key.size >= sizeof(keybuf)) { + rc = IW_ERROR_OVERFLOW; + goto finish; + } + val.data = binn_ptr(imeta); + val.size = binn_size(imeta); + rc = iwkv_put(db->metadb, &key, &val, 0); + RCGO(rc, finish); + + idx->next = jbc->idx; + jbc->idx = idx; + +finish: + if (rc) { + if (idx) { + if (idx->idb) { + iwkv_db_destroy(&idx->idb); + idx->idb = 0; + } + _jb_idx_release(idx); + } + } + free(ptr); + binn_free(imeta); + API_COLL_UNLOCK(jbc, rci, rc); + return rc; +} + +static iwrc _jb_patch( + EJDB db, const char *coll, int64_t id, bool upsert, + const char *patchjson, JBL_NODE patchjbn, JBL patchjbl) { + + int rci; + JBCOLL jbc; + struct _JBL sjbl; + JBL_NODE root, patch; + JBL ujbl = 0; + IWPOOL *pool = 0; + IWKV_val val = { 0 }; + IWKV_val key = { + .data = &id, + .size = sizeof(id) + }; + + iwrc rc = _jb_coll_acquire_keeplock(db, coll, true, &jbc); + RCGO(rc, finish); + + rc = iwkv_get(jbc->cdb, &key, &val); + if (upsert && (rc == IWKV_ERROR_NOTFOUND)) { + if (patchjson) { + rc = jbl_from_json(&ujbl, patchjson); + } else if (patchjbl) { + ujbl = patchjbl; + } else if (patchjbn) { + rc = jbl_from_node(&ujbl, patchjbn); + } else { + rc = IW_ERROR_INVALID_ARGS; + } + RCGO(rc, finish); + if (jbl_type(ujbl) != JBV_OBJECT) { + rc = EJDB_ERROR_PATCH_JSON_NOT_OBJECT; + goto finish; + } + rc = _jb_put_impl(jbc, ujbl, id); + if (!rc && (jbc->id_seq < id)) { + jbc->id_seq = id; + } + goto finish; + } else { + RCGO(rc, finish); + } + + rc = jbl_from_buf_keep_onstack(&sjbl, val.data, val.size); + RCGO(rc, finish); + + pool = iwpool_create_empty(); + if (!pool) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + + rc = jbl_to_node(&sjbl, &root, false, pool); + RCGO(rc, finish); + + if (patchjson) { + rc = jbn_from_json(patchjson, &patch, pool); + } else if (patchjbl) { + rc = jbl_to_node(patchjbl, &patch, false, pool); + } else if (patchjbn) { + patch = patchjbn; + } else { + rc = IW_ERROR_INVALID_ARGS; + } + RCGO(rc, finish); + + rc = jbn_patch_auto(root, patch, pool); + RCGO(rc, finish); + + if (root->type == JBV_OBJECT) { + rc = jbl_create_empty_object(&ujbl); + RCGO(rc, finish); + } else if (root->type == JBV_ARRAY) { + rc = jbl_create_empty_array(&ujbl); + RCGO(rc, finish); + } else { + rc = JBL_ERROR_CREATION; + goto finish; + } + rc = jbl_fill_from_node(ujbl, root); + RCGO(rc, finish); + + rc = _jb_put_impl(jbc, ujbl, id); + +finish: + API_COLL_UNLOCK(jbc, rci, rc); + if (ujbl != patchjbl) { + jbl_destroy(&ujbl); + } + if (val.data) { + iwkv_val_dispose(&val); + } + iwpool_destroy(pool); + return rc; +} + +static iwrc _jb_wal_lock_interceptor(bool before, void *op) { + int rci; + iwrc rc = 0; + EJDB db = op; + assert(db); + if (before) { + API_WLOCK2(db, rci); + } else { + API_UNLOCK(db, rci, rc); + } + return rc; +} + +iwrc ejdb_patch(EJDB db, const char *coll, const char *patchjson, int64_t id) { + return _jb_patch(db, coll, id, false, patchjson, 0, 0); +} + +iwrc ejdb_patch_jbn(EJDB db, const char *coll, JBL_NODE patch, int64_t id) { + return _jb_patch(db, coll, id, false, 0, patch, 0); +} + +iwrc ejdb_patch_jbl(EJDB db, const char *coll, JBL patch, int64_t id) { + return _jb_patch(db, coll, id, false, 0, 0, patch); +} + +iwrc ejdb_merge_or_put(EJDB db, const char *coll, const char *patchjson, int64_t id) { + return _jb_patch(db, coll, id, true, patchjson, 0, 0); +} + +iwrc ejdb_merge_or_put_jbn(EJDB db, const char *coll, JBL_NODE patch, int64_t id) { + return _jb_patch(db, coll, id, true, 0, patch, 0); +} + +iwrc ejdb_merge_or_put_jbl(EJDB db, const char *coll, JBL patch, int64_t id) { + return _jb_patch(db, coll, id, true, 0, 0, patch); +} + +iwrc ejdb_put(EJDB db, const char *coll, JBL jbl, int64_t id) { + if (!jbl) { + return IW_ERROR_INVALID_ARGS; + } + int rci; + JBCOLL jbc; + iwrc rc = _jb_coll_acquire_keeplock(db, coll, true, &jbc); + RCRET(rc); + rc = _jb_put_impl(jbc, jbl, id); + if (!rc && (jbc->id_seq < id)) { + jbc->id_seq = id; + } + API_COLL_UNLOCK(jbc, rci, rc); + return rc; +} + +static iwrc _jb_put_new_lw(JBCOLL jbc, JBL jbl, int64_t *id) { + iwrc rc = 0; + int64_t oid = jbc->id_seq + 1; + IWKV_val val, key = { + .data = &oid, + .size = sizeof(oid) + }; + struct _JBPHCTX pctx = { + .id = oid, + .jbc = jbc, + .jbl = jbl + }; + + RCC(rc, finish, jbl_as_buf(jbl, &val.data, &val.size)); + RCC(rc, finish, _jb_put_handler_after(iwkv_puth(jbc->cdb, &key, &val, 0, _jb_put_handler, &pctx), &pctx)); + + jbc->id_seq = oid; + if (id) { + *id = oid; + } + +finish: + return rc; +} + +iwrc ejdb_put_new(EJDB db, const char *coll, JBL jbl, int64_t *id) { + if (!jbl) { + return IW_ERROR_INVALID_ARGS; + } + int rci; + JBCOLL jbc; + if (id) { + *id = 0; + } + iwrc rc = _jb_coll_acquire_keeplock(db, coll, true, &jbc); + RCRET(rc); + + rc = _jb_put_new_lw(jbc, jbl, id); + + API_COLL_UNLOCK(jbc, rci, rc); + return rc; +} + +iwrc ejdb_put_new_jbn(EJDB db, const char *coll, JBL_NODE jbn, int64_t *id) { + JBL jbl = 0; + iwrc rc = jbl_from_node(&jbl, jbn); + RCRET(rc); + rc = ejdb_put_new(db, coll, jbl, id); + jbl_destroy(&jbl); + return rc; +} + +iwrc jb_get(EJDB db, const char *coll, int64_t id, jb_coll_acquire_t acm, JBL *jblp) { + if (!id || !jblp) { + return IW_ERROR_INVALID_ARGS; + } + *jblp = 0; + int rci; + JBCOLL jbc; + JBL jbl = 0; + IWKV_val val = { 0 }; + IWKV_val key = { .data = &id, .size = sizeof(id) }; + iwrc rc = _jb_coll_acquire_keeplock2(db, coll, acm, &jbc); + RCRET(rc); + + rc = iwkv_get(jbc->cdb, &key, &val); + RCGO(rc, finish); + rc = jbl_from_buf_keep(&jbl, val.data, val.size, false); + RCGO(rc, finish); + *jblp = jbl; + +finish: + if (rc) { + if (jbl) { + jbl_destroy(&jbl); + } else { + iwkv_val_dispose(&val); + } + } + API_COLL_UNLOCK(jbc, rci, rc); + return rc; +} + +iwrc ejdb_get(EJDB db, const char *coll, int64_t id, JBL *jblp) { + return jb_get(db, coll, id, JB_COLL_ACQUIRE_EXISTING, jblp); +} + +iwrc ejdb_del(EJDB db, const char *coll, int64_t id) { + int rci; + JBCOLL jbc; + struct _JBL jbl; + IWKV_val val = { 0 }; + IWKV_val key = { .data = &id, .size = sizeof(id) }; + iwrc rc = _jb_coll_acquire_keeplock2(db, coll, JB_COLL_ACQUIRE_WRITE | JB_COLL_ACQUIRE_EXISTING, &jbc); + RCRET(rc); + + rc = iwkv_get(jbc->cdb, &key, &val); + RCGO(rc, finish); + + rc = jbl_from_buf_keep_onstack(&jbl, val.data, val.size); + RCGO(rc, finish); + + for (JBIDX idx = jbc->idx; idx; idx = idx->next) { + IWRC(_jb_idx_record_remove(idx, id, &jbl), rc); + } + rc = iwkv_del(jbc->cdb, &key, 0); + RCGO(rc, finish); + _jb_meta_nrecs_update(jbc->db, jbc->dbid, -1); + jbc->rnum -= 1; + +finish: + if (val.data) { + iwkv_val_dispose(&val); + } + API_COLL_UNLOCK(jbc, rci, rc); + return rc; +} + +iwrc jb_del(JBCOLL jbc, JBL jbl, int64_t id) { + iwrc rc = 0; + IWKV_val key = { .data = &id, .size = sizeof(id) }; + for (JBIDX idx = jbc->idx; idx; idx = idx->next) { + IWRC(_jb_idx_record_remove(idx, id, jbl), rc); + } + rc = iwkv_del(jbc->cdb, &key, 0); + RCRET(rc); + _jb_meta_nrecs_update(jbc->db, jbc->dbid, -1); + jbc->rnum -= 1; + return rc; +} + +iwrc jb_cursor_del(JBCOLL jbc, IWKV_cursor cur, int64_t id, JBL jbl) { + iwrc rc = 0; + for (JBIDX idx = jbc->idx; idx; idx = idx->next) { + IWRC(_jb_idx_record_remove(idx, id, jbl), rc); + } + rc = iwkv_cursor_del(cur, 0); + RCRET(rc); + _jb_meta_nrecs_update(jbc->db, jbc->dbid, -1); + jbc->rnum -= 1; + return rc; +} + +iwrc ejdb_ensure_collection(EJDB db, const char *coll) { + int rci; + JBCOLL jbc; + iwrc rc = _jb_coll_acquire_keeplock(db, coll, false, &jbc); + RCRET(rc); + API_COLL_UNLOCK(jbc, rci, rc); + return rc; +} + +iwrc ejdb_remove_collection(EJDB db, const char *coll) { + int rci; + iwrc rc = 0; + if (db->oflags & IWKV_RDONLY) { + return IW_ERROR_READONLY; + } + API_WLOCK(db, rci); + JBCOLL jbc; + IWKV_val key; + char keybuf[sizeof(KEY_PREFIX_IDXMETA) + 1 + 2 * JBNUMBUF_SIZE]; // Full key format: i.. + khiter_t k = kh_get(JBCOLLM, db->mcolls, coll); + + if (k != kh_end(db->mcolls)) { + + jbc = kh_value(db->mcolls, k); + key.data = keybuf; + key.size = snprintf(keybuf, sizeof(keybuf), KEY_PREFIX_COLLMETA "%u", jbc->dbid); + rc = iwkv_del(jbc->db->metadb, &key, IWKV_SYNC); + RCGO(rc, finish); + + _jb_meta_nrecs_removedb(db, jbc->dbid); + + for (JBIDX idx = jbc->idx; idx; idx = idx->next) { + key.data = keybuf; + key.size = snprintf(keybuf, sizeof(keybuf), KEY_PREFIX_IDXMETA "%u" "." "%u", jbc->dbid, idx->dbid); + rc = iwkv_del(jbc->db->metadb, &key, 0); + RCGO(rc, finish); + _jb_meta_nrecs_removedb(db, idx->dbid); + } + for (JBIDX idx = jbc->idx, nidx; idx; idx = nidx) { + IWRC(iwkv_db_destroy(&idx->idb), rc); + idx->idb = 0; + nidx = idx->next; + _jb_idx_release(idx); + } + jbc->idx = 0; + IWRC(iwkv_db_destroy(&jbc->cdb), rc); + kh_del(JBCOLLM, db->mcolls, k); + _jb_coll_release(jbc); + } + +finish: + API_UNLOCK(db, rci, rc); + return rc; +} + +iwrc jb_collection_join_resolver(int64_t id, const char *coll, JBL *out, JBEXEC *ctx) { + assert(out && ctx && coll); + EJDB db = ctx->jbc->db; + return jb_get(db, coll, id, JB_COLL_ACQUIRE_EXISTING, out); +} + +int jb_proj_node_cache_cmp(const void *v1, const void *v2) { + const struct _JBDOCREF *r1 = v1; + const struct _JBDOCREF *r2 = v2; + int ret = r1->id > r2->id ? 1 : r1->id < r2->id ? -1 : 0; + if (!ret) { + return strcmp(r1->coll, r2->coll); + ; + } + return ret; +} + +void jb_proj_node_kvfree(void *key, void *val) { + free(key); +} + +iwrc ejdb_rename_collection(EJDB db, const char *coll, const char *new_coll) { + if (!coll || !new_coll) { + return IW_ERROR_INVALID_ARGS; + } + int rci; + iwrc rc = 0; + if (db->oflags & IWKV_RDONLY) { + return IW_ERROR_READONLY; + } + IWKV_val key, val; + JBL nmeta = 0, jbv = 0; + char keybuf[JBNUMBUF_SIZE + sizeof(KEY_PREFIX_COLLMETA)]; + + API_WLOCK(db, rci); + + khiter_t k = kh_get(JBCOLLM, db->mcolls, coll); + if (k == kh_end(db->mcolls)) { + rc = EJDB_ERROR_COLLECTION_NOT_FOUND; + goto finish; + } + khiter_t k2 = kh_get(JBCOLLM, db->mcolls, new_coll); + if (k2 != kh_end(db->mcolls)) { + rc = EJDB_ERROR_TARGET_COLLECTION_EXISTS; + goto finish; + } + + JBCOLL jbc = kh_value(db->mcolls, k); + + rc = jbl_create_empty_object(&nmeta); + RCGO(rc, finish); + + if (!binn_object_set_str(&nmeta->bn, "name", new_coll)) { + rc = JBL_ERROR_CREATION; + goto finish; + } + if (!binn_object_set_uint32(&nmeta->bn, "id", jbc->dbid)) { + rc = JBL_ERROR_CREATION; + goto finish; + } + + rc = jbl_as_buf(nmeta, &val.data, &val.size); + RCGO(rc, finish); + key.size = snprintf(keybuf, sizeof(keybuf), KEY_PREFIX_COLLMETA "%u", jbc->dbid); + if (key.size >= sizeof(keybuf)) { + rc = IW_ERROR_OVERFLOW; + goto finish; + } + key.data = keybuf; + + rc = jbl_at(nmeta, "/name", &jbv); + RCGO(rc, finish); + + const char *new_name = jbl_get_str(jbv); + + rc = iwkv_put(db->metadb, &key, &val, IWKV_SYNC); + RCGO(rc, finish); + + kh_del(JBCOLLM, db->mcolls, k); + k2 = kh_put(JBCOLLM, db->mcolls, new_name, &rci); + if (rci != -1) { + kh_value(db->mcolls, k2) = jbc; + } else { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + + jbc->name = new_name; + jbl_destroy(&jbc->meta); + jbc->meta = nmeta; + +finish: + if (jbv) { + jbl_destroy(&jbv); + } + if (rc) { + if (nmeta) { + jbl_destroy(&nmeta); + } + } + API_UNLOCK(db, rci, rc); + return rc; +} + +iwrc ejdb_get_meta(EJDB db, JBL *jblp) { + int rci; + *jblp = 0; + JBL jbl; + iwrc rc = jbl_create_empty_object(&jbl); + RCRET(rc); + binn *clist = 0; + API_RLOCK(db, rci); + if (!binn_object_set_str(&jbl->bn, "version", ejdb_version_full())) { + rc = JBL_ERROR_CREATION; + goto finish; + } + IWFS_FSM_STATE sfsm; + rc = iwkv_state(db->iwkv, &sfsm); + RCRET(rc); + if ( !binn_object_set_str(&jbl->bn, "file", sfsm.exfile.file.opts.path) + || !binn_object_set_int64(&jbl->bn, "size", sfsm.exfile.fsize)) { + rc = JBL_ERROR_CREATION; + goto finish; + } + clist = binn_list(); + if (!clist) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + for (khiter_t k = kh_begin(db->mcolls); k != kh_end(db->mcolls); ++k) { + if (!kh_exist(db->mcolls, k)) { + continue; + } + JBCOLL jbc = kh_val(db->mcolls, k); + rc = _jb_coll_add_meta_lr(jbc, clist); + RCGO(rc, finish); + } + if (!binn_object_set_list(&jbl->bn, "collections", clist)) { + rc = JBL_ERROR_CREATION; + goto finish; + } + binn_free(clist); + clist = 0; + +finish: + API_UNLOCK(db, rci, rc); + if (rc) { + if (clist) { + binn_free(clist); + } + jbl_destroy(&jbl); + } else { + *jblp = jbl; + } + return rc; +} + +iwrc ejdb_online_backup(EJDB db, uint64_t *ts, const char *target_file) { + ENSURE_OPEN(db); + return iwkv_online_backup(db->iwkv, ts, target_file); +} + +iwrc ejdb_get_iwkv(EJDB db, IWKV *kvp) { + if (!db || !kvp) { + return IW_ERROR_INVALID_ARGS; + } + *kvp = db->iwkv; + return 0; +} + +iwrc ejdb_open(const EJDB_OPTS *_opts, EJDB *ejdbp) { + *ejdbp = 0; + int rci; + iwrc rc = ejdb_init(); + RCRET(rc); + if (!_opts || !_opts->kv.path || !ejdbp) { + return IW_ERROR_INVALID_ARGS; + } + + EJDB db = calloc(1, sizeof(*db)); + if (!db) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + + memcpy(&db->opts, _opts, sizeof(db->opts)); + if (!db->opts.sort_buffer_sz) { + db->opts.sort_buffer_sz = 16 * 1024 * 1024; // 16Mb + } + if (db->opts.sort_buffer_sz < 1024 * 1024) { // Min 1Mb + db->opts.sort_buffer_sz = 1024 * 1024; + } + if (!db->opts.document_buffer_sz) { // 64Kb + db->opts.document_buffer_sz = 64 * 1024; + } + if (db->opts.document_buffer_sz < 16 * 1024) { // Min 16Kb + db->opts.document_buffer_sz = 16 * 1024; + } + EJDB_HTTP *http = &db->opts.http; + if (http->bind) { + http->bind = strdup(http->bind); + } + if (http->access_token) { + http->access_token = strdup(http->access_token); + if (!http->access_token) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + http->access_token_len = strlen(http->access_token); + } + + pthread_rwlockattr_t attr; + pthread_rwlockattr_init(&attr); +#if defined __linux__ && (defined __USE_UNIX98 || defined __USE_XOPEN2K) + pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); +#endif + rci = pthread_rwlock_init(&db->rwl, &attr); + if (rci) { + rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci); + free(db); + return rc; + } + db->mcolls = kh_init(JBCOLLM); + if (!db->mcolls) { + rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci); + goto finish; + } + + IWKV_OPTS kvopts; + memcpy(&kvopts, &db->opts.kv, sizeof(db->opts.kv)); + kvopts.wal.enabled = !db->opts.no_wal; + kvopts.wal.wal_lock_interceptor = _jb_wal_lock_interceptor; + kvopts.wal.wal_lock_interceptor_opaque = db; + + rc = iwkv_open(&kvopts, &db->iwkv); + RCGO(rc, finish); + + db->oflags = kvopts.oflags; + rc = _jb_db_meta_load(db); + RCGO(rc, finish); + + if (db->opts.http.enabled) { + // Maximum WS/HTTP API body size. Default: 64Mb, Min: 512K + if (!db->opts.http.max_body_size) { + db->opts.http.max_body_size = 64 * 1024 * 1024; + } else if (db->opts.http.max_body_size < 512 * 1024) { + db->opts.http.max_body_size = 512 * 1024; + } + } + +#ifdef JB_HTTP + if (db->opts.http.enabled && !db->opts.http.blocking) { + rc = jbr_start(db, &db->opts, &db->jbr); + RCGO(rc, finish); + } +#endif + +finish: + if (rc) { + _jb_db_release(&db); + } else { + db->open = true; + *ejdbp = db; +#ifdef JB_HTTP + if (db->opts.http.enabled && db->opts.http.blocking) { + rc = jbr_start(db, &db->opts, &db->jbr); + } +#endif + } + return rc; +} + +iwrc ejdb_close(EJDB *ejdbp) { + if (!ejdbp || !*ejdbp) { + return IW_ERROR_INVALID_ARGS; + } + EJDB db = *ejdbp; + if (!__sync_bool_compare_and_swap(&db->open, 1, 0)) { + iwlog_error2("Database is closed already"); + return IW_ERROR_INVALID_STATE; + } + iwrc rc = _jb_db_release(ejdbp); + return rc; +} + +const char *ejdb_git_revision(void) { + return EJDB2_GIT_REVISION; +} + +const char *ejdb_version_full(void) { + return EJDB2_VERSION; +} + +unsigned int ejdb_version_major(void) { + return EJDB2_VERSION_MAJOR; +} + +unsigned int ejdb_version_minor(void) { + return EJDB2_VERSION_MINOR; +} + +unsigned int ejdb_version_patch(void) { + return EJDB2_VERSION_PATCH; +} + +static const char *_ejdb_ecodefn(locale_t locale, uint32_t ecode) { + if (!((ecode > _EJDB_ERROR_START) && (ecode < _EJDB_ERROR_END))) { + return 0; + } + switch (ecode) { + case EJDB_ERROR_INVALID_COLLECTION_META: + return "Invalid collection metadata (EJDB_ERROR_INVALID_COLLECTION_META)"; + case EJDB_ERROR_INVALID_COLLECTION_INDEX_META: + return "Invalid collection index metadata (EJDB_ERROR_INVALID_COLLECTION_INDEX_META)"; + case EJDB_ERROR_INVALID_INDEX_MODE: + return "Invalid index mode specified (EJDB_ERROR_INVALID_INDEX_MODE)"; + case EJDB_ERROR_MISMATCHED_INDEX_UNIQUENESS_MODE: + return "Index exists but mismatched uniqueness constraint (EJDB_ERROR_MISMATCHED_INDEX_UNIQUENESS_MODE)"; + case EJDB_ERROR_UNIQUE_INDEX_CONSTRAINT_VIOLATED: + return "Unique index constraint violated (EJDB_ERROR_UNIQUE_INDEX_CONSTRAINT_VIOLATED)"; + case EJDB_ERROR_INVALID_COLLECTION_NAME: + return "Invalid collection name (EJDB_ERROR_INVALID_COLLECTION_NAME)"; + case EJDB_ERROR_COLLECTION_NOT_FOUND: + return "Collection not found (EJDB_ERROR_COLLECTION_NOT_FOUND)"; + case EJDB_ERROR_TARGET_COLLECTION_EXISTS: + return "Target collection exists (EJDB_ERROR_TARGET_COLLECTION_EXISTS)"; + case EJDB_ERROR_PATCH_JSON_NOT_OBJECT: + return "Patch JSON must be an object (map) (EJDB_ERROR_PATCH_JSON_NOT_OBJECT)"; + } + return 0; +} + +iwrc ejdb_init() { + static volatile int jb_initialized = 0; + if (!__sync_bool_compare_and_swap(&jb_initialized, 0, 1)) { + return 0; // initialized already + } + iwrc rc = iw_init(); + RCRET(rc); + rc = jbl_init(); + RCRET(rc); + rc = jql_init(); + RCRET(rc); +#ifdef JB_HTTP + rc = jbr_init(); + RCRET(rc); +#endif + return iwlog_register_ecodefn(_ejdb_ecodefn); +} diff --git a/src/ejdb2.h b/src/ejdb2.h new file mode 100644 index 0000000..5a3a3f7 --- /dev/null +++ b/src/ejdb2.h @@ -0,0 +1,702 @@ +#pragma once +#ifndef EJDB2_H +#define EJDB2_H + +/************************************************************************************************** + * EJDB2 + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +#include +#include "jql.h" +#include "jbl.h" + +IW_EXTERN_C_START + +/** + * @brief ejdb2 initialization routine. + * @note Must be called before using any of ejdb API function. + */ +IW_EXPORT WUR iwrc ejdb_init(void); + +/** Maximum length of EJDB collection name */ +#define EJDB_COLLECTION_NAME_MAX_LEN 255 + +/** + * @brief EJDB error codes. + */ +typedef enum { + _EJDB_ERROR_START = (IW_ERROR_START + 15000UL), + EJDB_ERROR_INVALID_COLLECTION_NAME, /**< Invalid collection name */ + EJDB_ERROR_INVALID_COLLECTION_META, /**< Invalid collection metadata */ + EJDB_ERROR_INVALID_COLLECTION_INDEX_META, /**< Invalid collection index metadata */ + EJDB_ERROR_INVALID_INDEX_MODE, /**< Invalid index mode specified */ + EJDB_ERROR_MISMATCHED_INDEX_UNIQUENESS_MODE, /**< Index exists but mismatched uniqueness constraint */ + EJDB_ERROR_UNIQUE_INDEX_CONSTRAINT_VIOLATED, /**< Unique index constraint violated */ + EJDB_ERROR_COLLECTION_NOT_FOUND, /**< Collection not found */ + EJDB_ERROR_TARGET_COLLECTION_EXISTS, /**< Target collection exists */ + EJDB_ERROR_PATCH_JSON_NOT_OBJECT, /**< Patch JSON must be an object (map) */ + _EJDB_ERROR_END, +} ejdb_ecode_t; + +/** Index creation mode */ +typedef uint8_t ejdb_idx_mode_t; + +/** Marks index is unique, no duplicated values allowed. */ +#define EJDB_IDX_UNIQUE ((ejdb_idx_mode_t) 0x01U) + +/** Index values have string type. + * Type conversion will be performed on atempt to save value with other type */ +#define EJDB_IDX_STR ((ejdb_idx_mode_t) 0x04U) + +/** Index values have signed integer 64 bit wide type. + * Type conversion will be performed on atempt to save value with other type */ +#define EJDB_IDX_I64 ((ejdb_idx_mode_t) 0x08U) + +/** Index value have floating point type. + * @note Internally floating point numbers are converted to string + * with precision of 6 digits after decimal point. + */ +#define EJDB_IDX_F64 ((ejdb_idx_mode_t) 0x10U) + +/** + * @brief Database handler. + */ +struct _EJDB; +typedef struct _EJDB*EJDB; + +/** + * @brief EJDB HTTP/Websocket Server options. + */ +typedef struct _EJDB_HTTP { + bool enabled; /**< If HTTP/Websocket endpoint enabled. Default: false */ + int port; /**< Listen port number, required */ + const char *bind; /**< Listen IP/host. Default: `localhost` */ + const char *access_token; /**< Server access token passed in `X-Access-Token` header. Default: zero */ + size_t access_token_len; /**< Length of access token string. Default: zero */ + bool blocking; /**< Block `ejdb_open()` thread until http service finished. + Otherwise HTTP server will be started in background. */ + bool read_anon; /**< Allow anonymous read-only database access */ + size_t max_body_size; /**< Maximum WS/HTTP API body size. Default: 64Mb, Min: 512K */ + bool cors; /**< Allow CORS */ +} EJDB_HTTP; + +/** + * @brief EJDB open options. + */ +typedef struct _EJDB_OPTS { + IWKV_OPTS kv; /**< IWKV storage options. @see iwkv.h */ + EJDB_HTTP http; /**< HTTP/Websocket server options */ + bool no_wal; /**< Do not use write-ahead-log. Default: false */ + uint32_t sort_buffer_sz; /**< Max sorting buffer size. If exceeded an overflow temp file for sorted data will + created. + Default 16Mb, min: 1Mb */ + uint32_t document_buffer_sz; /**< Initial size of buffer in bytes used to process/store document during query + execution. + Default 64Kb, min: 16Kb */ +} EJDB_OPTS; + +/** + * @brief Document representation as result of query execution. + * @see ejdb_exec() + */ +typedef struct _EJDB_DOC { + int64_t id; /**< Document ID. Not zero. */ + JBL raw; /**< JSON document in compact binary form. + Based on [Binn](https://github.com/liteserver/binn) format. + Not zero. */ + JBL_NODE node; /**< JSON document as in-memory tree. Not zero only if query has `apply` or `projection` + parts. + + @warning The lifespan of @ref EJDB_DOC.node will be valid only during the call of + @ref EJDB_EXEC_VISITOR + It is true in all cases EXCEPT: + - @ref EJDB_EXEC.pool is not set by `ejdb_exec()` caller + - One of `ejdb_list()` methods used */ + + struct _EJDB_DOC *next; /**< Reference to next document in result list or zero. + Makes sense only for `ejdb_list()` calls. */ + struct _EJDB_DOC *prev; /**< Reference to the previous document in result list or zero. + Makes sense only for `ejdb_list()` calls. */ +} *EJDB_DOC; + +/** + * @brief Query result as list. + * Used as result of `ejdb_list()` query functions. + * + * @warning Getting result of query as list can be very memory consuming for large collections. + * Consider use of `ejdb_exec()` with visitor or set `limit` for query. + */ +typedef struct _EJDB_LIST { + EJDB db; /**< EJDB storage used for query execution. Not zero. */ + JQL q; /**< Query executed. Not zero. */ + EJDB_DOC first; /**< First document in result list. Zero if result set is empty. */ + IWPOOL *pool; /**< Memory pool used to store list of documents */ +} *EJDB_LIST; + +struct _EJDB_EXEC; + +/** + * @brief Visitor for matched documents during query execution. + * + * @param ctx Visitor context. + * @param doc Data in `doc` is valid only during execution of this method, to keep a data for farther + * processing you need to copy it. + * @param step [out] Move forward cursor to given number of steps, `1` by default. + */ +typedef iwrc (*EJDB_EXEC_VISITOR)(struct _EJDB_EXEC *ctx, EJDB_DOC doc, int64_t *step); + +/** + * @brief Query execution context. + * Passed to `ejdb_exec()` to execute database query. + */ +typedef struct _EJDB_EXEC { + EJDB db; /**< EJDB database object. Required. */ + JQL q; /**< Query object to be executed. Created by `jql_create()` Required. */ + EJDB_EXEC_VISITOR visitor; /**< Optional visitor to handle documents in result set. */ + void *opaque; /**< Optional user data passed to visitor functions. */ + int64_t skip; /**< Number of records to skip. Takes precedence over `skip` encoded in query. */ + int64_t limit; /**< Result set size limitation. Zero means no limitations. Takes precedence over `limit` + encoded in query. */ + int64_t cnt; /**< Number of result documents processed by `visitor` */ + IWXSTR *log; /**< Optional query execution log buffer. If set major query execution/index selection + steps will be logged into */ + IWPOOL *pool; /**< Optional pool which can be used in query apply */ +} EJDB_EXEC; + +/** + * @brief Open storage file. + * + * Storage can be opened only by one single process at time. + * + * @param opts Operating options. Can be stack allocated or disposed after `ejdb_open()` call. + * @param [out] ejdbp EJDB storage handle pointer as result. + */ +IW_EXPORT WUR iwrc ejdb_open(const EJDB_OPTS *opts, EJDB *ejdbp); + +/** + * @brief Closes storage and frees up all resources. + * @param [in,out] ejdbp Pointer to storage handle, will set to zero oncompletion. + * + * @return `0` on success. + * Any non zero error codes. + */ +IW_EXPORT iwrc ejdb_close(EJDB *ejdbp); + +/** + * @brief Executes a query. + * + * The `ux` structure should be configured before this call, + * usually it is a stack allocated object. + * + * Query object should be created by `jql_create()`. + * + * Example: + * + * @code {.c} + * + * JQL q; + * iwrc rc = jql_create(&q, "mycollection", "/[firstName=?]"); + * RCRET(rc); + * + * jql_set_str(q, 0, 0, "Andy"); // Set positional string query parameter + * + * EJDB_EXEC ux = { + * .db = db, + * .q = q, + * .visitor = my_query_results_visitor_func + * }; + * rc = ejdb_exec(&ux); // Execute query + * jql_destroy(&q); // Destroy query object + * + * @endcode + * + * Query object can be reused in many `ejdb_exec()` calls + * with different positional/named parameters. + * + * @param [in] ux Query execution params, object state may be changes during query execution. + * Not zero. + * + * @return `0` on success. + * Any non zero error codes. + */ +IW_EXPORT WUR iwrc ejdb_exec(EJDB_EXEC *ux); + +/** + * @brief Executes a given query and builds a query result as linked list of documents. + * + * @warning Getting whole query result as linked list can be memory consuming for large collections. + * Consider use of `ejdb_exec()` with visitor or set a positive `limit` value. + * + * @param db Database handle. Not zero. + * @param q Query object. Not zero. + * @param [out] first First document in result set or zero if no matched documents found. + * @param limit Maximum number of documents in result set. Takes precedence over `limit` encoded in query. + * Zero means a `limit` encoded in `q` will be used. + * @param pool Memory pool will keep documents allocated in result set. Not zero. + * + * @return `0` on success. + * Any non zero error codes. + */ +IW_EXPORT WUR iwrc ejdb_list(EJDB db, JQL q, EJDB_DOC *first, int64_t limit, IWPOOL *pool); + +/** + * @brief Executes a given query `q` then returns `count` of matched documents. + * + * @param db Database handle. Not zero. + * @param q Query object. Not zero. + * @param [out] count Placeholder for number of matched documents. Not zero. + * @param limit Limit of matched rows. Makes sense for update queries. + * + */ +IW_EXPORT WUR iwrc ejdb_count(EJDB db, JQL q, int64_t *count, int64_t limit); + +/** + * @brief Executes a given query `q` then returns `count` of matched documents. + * + * @param db Database handle. Not zero. + * @param coll Name of document collection. + * Can be zero, in what collection name should be encoded in query. + * @param q Query text. Not zero. + * @param [out] count Placeholder for number of matched documents. Not zero. + * @param limit Limit of matched rows. Makes sense for update queries. + * + */ +IW_EXPORT WUR iwrc ejdb_count2(EJDB db, const char *coll, const char *q, int64_t *count, int64_t limit); + +/** + * @brief Executes update query assuming that query object contains `apply` clause. + * Similar to `ejdb_count`. + * + * @param db Database handle. Not zero. + * @param q Query object. Not zero. + */ +IW_EXPORT WUR iwrc ejdb_update(EJDB db, JQL q); + +/** + * @brief Executes update query assuming that query object contains `apply` clause. + * Similar to `ejdb_count`. + * + * @param db Database handle. Not zero. + * @param coll Name of document collection. + * Can be zero, in what collection name should be encoded in query. + * @param q Query text. Not zero. + */ +IW_EXPORT WUR iwrc ejdb_update2(EJDB db, const char *coll, const char *q); + +/** + * @brief Executes a given `query` and builds a result as linked list of documents. + * + * @note Returned `listp` must be disposed by `ejdb_list_destroy()` + * @warning Getting whole query result as linked list can be memory consuming for large collections. + * Consider use of `ejdb_exec()` with visitor or set a positive `limit` value. + * + * @param db Database handle. Not zero. + * @param coll Collection name. If zero, collection name must be encoded in query. + * @param query Query text. Not zero. + * @param limit Maximum number of documents in result set. Takes precedence over `limit` encoded in query. + * Zero means a `limit` encoded in `query` will be used. + * @param [out] listp Holder for query result, should be disposed by `ejdb_list_destroy()`. + * Not zero. + * + * @return `0` on success. + * Any non zero error codes. + */ +IW_EXPORT WUR iwrc ejdb_list2(EJDB db, const char *coll, const char *query, int64_t limit, EJDB_LIST *listp); + +/** + * @brief Executes a given `query` and builds a result as linked list of documents. + * + * @note Returned `listp` must be disposed by `ejdb_list_destroy()` + * @warning Getting whole query result as linked list can be memory consuming for large collections. + * Consider use of `ejdb_exec()` with visitor or set a positive `limit` value. + * + * @param db Database handle. Not zero. + * @param coll Collection name. If zero, collection name must be encoded in query. + * @param query Query text. Not zero. + * @param limit Maximum number of documents in result set. Takes precedence over `limit` encoded in query. + * @param log Optional buffer to collect query execution / index usage info. + * @param [out] listp Holder for query result, should be disposed by `ejdb_list_destroy()`. + * Not zero. + * + * @return `0` on success. + * Any non zero error codes. + */ +IW_EXPORT WUR iwrc ejdb_list3( + EJDB db, const char *coll, const char *query, int64_t limit, + IWXSTR *log, EJDB_LIST *listp); + +/** + * @brief Executes a given query `q` and builds a result as linked list of documents (`listp`). + * + * @note Returned `listp` must be disposed by `ejdb_list_destroy()` + * @warning Getting whole query result as linked list can be memory consuming for large collections. + * Consider use of `ejdb_exec()` with visitor or set a positive `limit` value. + * + * @param db Database handle. Not zero. + * @param q Query object. Not zero. + * @param limit Maximum number of documents in result set. Takes precedence over `limit` encoded in query. + * @param log Optional buffer to collect query execution / index usage info. + * @param [out] listp Holder for query result, should be disposed by `ejdb_list_destroy()`. + * Not zero. + * + * @return `0` on success. + * Any non zero error codes. + */ +IW_EXPORT WUR iwrc ejdb_list4(EJDB db, JQL q, int64_t limit, IWXSTR *log, EJDB_LIST *listp); + +/** + * @brief Destroy query result set and set `listp` to zero. + * @param [in,out] listp Can be zero. + */ +IW_EXPORT void ejdb_list_destroy(EJDB_LIST *listp); + +/** + * @brief Apply rfc6902/rfc7396 JSON patch to the document identified by `id`. + * + * @param db Database handle. Not zero. + * @param coll Collection name. Not zero. + * @param patchjson JSON patch conformed to rfc6902 or rfc7396 specification. + * @param id Document id. Not zero. + * + * @return `0` on success. + * `IWKV_ERROR_NOTFOUND` if document not found. + * Any non zero error codes. + */ +IW_EXPORT WUR iwrc ejdb_patch(EJDB db, const char *coll, const char *patchjson, int64_t id); + +/** + * @brief Apply rfc6902/rfc7396 JSON patch to the document identified by `id`. + * + * @param db Database handle. Not zero. + * @param coll Collection name. Not zero. + * @param patch JSON patch conformed to rfc6902 or rfc7396 specification. + * @param id Document id. Not zero. + * + * @return `0` on success. + * `IWKV_ERROR_NOTFOUND` if document not found. + * Any non zero error codes. + */ +IW_EXPORT WUR iwrc ejdb_patch_jbn(EJDB db, const char *coll, JBL_NODE patch, int64_t id); + + +/** + * @brief Apply rfc6902/rfc7396 JSON patch to the document identified by `id`. + * + * @param db Database handle. Not zero. + * @param coll Collection name. Not zero. + * @param patch JSON patch conformed to rfc6902 or rfc7396 specification. + * @param id Document id. Not zero. + * + * @return `0` on success. + * `IWKV_ERROR_NOTFOUND` if document not found. + * Any non zero error codes. + */ +IW_EXPORT WUR iwrc ejdb_patch_jbl(EJDB db, const char *coll, JBL patch, int64_t id); + +/** + * @brief Apply JSON merge patch (rfc7396) to the document identified by `id` or + * insert new document under specified `id`. + * @note This is an atomic operation. + * + * @param db Database handle. Not zero. + * @param coll Collection name. Not zero. + * @param patchjson JSON merge patch conformed to rfc7396 specification. + * @param id Document id. Not zero. + * + */ +IW_EXPORT WUR iwrc ejdb_merge_or_put(EJDB db, const char *coll, const char *patchjson, int64_t id); + +/** + * @brief Apply JSON merge patch (rfc7396) to the document identified by `id` or + * insert new document under specified `id`. + * @note This is an atomic operation. + * + * @param db Database handle. Not zero. + * @param coll Collection name. Not zero. + * @param patch JSON merge patch conformed to rfc7396 specification. + * @param id Document id. Not zero. + * + */ +IW_EXPORT WUR iwrc ejdb_merge_or_put_jbn(EJDB db, const char *coll, JBL_NODE patch, int64_t id); + +/** + * @brief Apply JSON merge patch (rfc7396) to the document identified by `id` or + * insert new document under specified `id`. + * @note This is an atomic operation. + * + * @param db Database handle. Not zero. + * @param coll Collection name. Not zero. + * @param patch JSON merge patch conformed to rfc7396 specification. + * @param id Document id. Not zero. + * + */ +IW_EXPORT WUR iwrc ejdb_merge_or_put_jbl(EJDB db, const char *coll, JBL patch, int64_t id); + +/** + * @brief Save a given `jbl` document under specified `id`. + * + * @param db Database handle. Not zero. + * @param coll Collection name. Not zero. + * @param jbl JSON document. Not zero. + * @param id Document identifier. Not zero. + * + * @return `0` on success. + * Any non zero error codes. + */ +IW_EXPORT WUR iwrc ejdb_put(EJDB db, const char *coll, JBL jbl, int64_t id); + +/** + * @brief Save a document into `coll` under new identifier. + * + * @param db Database handle. Not zero. + * @param coll Collection name. Not zero. + * @param jbl JSON document. Not zero. + * @param [out] oid Placeholder for new document id. Not zero. + * + * @return `0` on success. + * Any non zero error codes. + */ +IW_EXPORT WUR iwrc ejdb_put_new(EJDB db, const char *coll, JBL jbl, int64_t *oid); + +/** + * @brief Save a document into `coll` under new identifier. + * + * @param db Database handle. Not zero. + * @param coll Collection name. Not zero. + * @param jbn JSON document. Not zero. + * @param [out] oid Placeholder for new document id. Not zero. + * + * @return `0` on success. + * Any non zero error codes. + */ +IW_EXPORT iwrc ejdb_put_new_jbn(EJDB db, const char *coll, JBL_NODE jbn, int64_t *id); + +/** + * @brief Retrieve document identified by given `id` from collection `coll`. + * + * @param db Database handle. Not zero. + * @param coll Collection name. Not zero. + * @param id Document id. Not zero. + * @param [out] jblp Placeholder for document. + * Must be released by `jbl_destroy()` + * + * @return `0` on success. + * `IWKV_ERROR_NOTFOUND` if document not found. + * `IW_ERROR_NOT_EXISTS` if collection `coll` is not exists in db. + * Any non zero error codes. + */ +IW_EXPORT WUR iwrc ejdb_get(EJDB db, const char *coll, int64_t id, JBL *jblp); + +/** + * @brief Remove document identified by given `id` from collection `coll`. + * + * @param db Database handle. Not zero. + * @param coll Collection name. Not zero. + * @param id Document id. Not zero. + * + * @return `0` on success. + * `IWKV_ERROR_NOTFOUND` if document not found. + * Any non zero error codes. + */ +IW_EXPORT iwrc ejdb_del(EJDB db, const char *coll, int64_t id); + +/** + * @brief Remove collection under the given name `coll`. + * + * @param db Database handle. Not zero. + * @param coll Collection name. Not zero. + * + * @return `0` on success. + * Will return `0` if collection is not found. + * Any non zero error codes. + */ +IW_EXPORT iwrc ejdb_remove_collection(EJDB db, const char *coll); + +/** + * @brief Rename collection `coll` to `new_coll`. + * + * @param db Database handle. Not zero. + * @param coll Old collection name. Not zero. + * @param new_coll New collection name. + * @return `0` on success. + * - `EJDB_ERROR_COLLECTION_NOT_FOUND` - if source `coll` is not found. + * - `EJDB_ERROR_TARGET_COLLECTION_EXISTS` - if `new_coll` is exists already. + * - Any other non zero error codes. + */ +IW_EXPORT iwrc ejdb_rename_collection(EJDB db, const char *coll, const char *new_coll); + +/** + * @brief Create collection with given name if it has not existed before + * + * @param db Database handle. Not zero. + * @param coll Collection name. Not zero. + * + * @return `0` on success. + * Any non zero error codes. + */ +IW_EXPORT iwrc ejdb_ensure_collection(EJDB db, const char *coll); + +/** + * @brief Create index with specified parameters if it has not existed before. + * + * @note Index `path` must be fully specified as rfc6901 JSON pointer + * and must not countain unspecified `*`/`**` element in middle sections. + * @see ejdb_idx_mode_t. + * + * Example document: + * + * @code + * { + * "address" : { + * "street": "High Street" + * } + * } + * @endcode + * + * Create unique index over all street names in nested address object: + * + * @code {.c} + * iwrc rc = ejdb_ensure_index(db, "mycoll", "/address/street", EJDB_IDX_UNIQUE | EJDB_IDX_STR); + * @endcode + * + * @param db Database handle. Not zero. + * @param coll Collection name. Not zero. + * @param path rfc6901 JSON pointer to indexed field. + * @param mode Index mode. + * + * @return `0` on success. + * `EJDB_ERROR_INVALID_INDEX_MODE` Invalid `mode` specified + * `EJDB_ERROR_MISMATCHED_INDEX_UNIQUENESS_MODE` trying to create non unique index over existing unique or vice + * versa. + * Any non zero error codes. + * + */ +IW_EXPORT iwrc ejdb_ensure_index(EJDB db, const char *coll, const char *path, ejdb_idx_mode_t mode); + +/** + * @brief Remove index if it has existed before. + * + * @param db Database handle. Not zero. + * @param coll Collection name. Not zero. + * @param path rfc6901 JSON pointer to indexed field. + * @param mode Index mode. + * + * @return `0` on success. + * Will return `0` if collection is not found. + * Any non zero error codes. + */ +IW_EXPORT iwrc ejdb_remove_index(EJDB db, const char *coll, const char *path, ejdb_idx_mode_t mode); + +/** + * @brief Returns JSON document describind database structure. + * @note Returned `jblp` must be disposed by `jbl_destroy()` + * + * Example database metadata: + * @code {.json} + * + * { + * "version": "2.0.0", // EJDB engine version + * "file": "db.jb", // Path to storage file + * "size": 16384, // Storage file size in bytes + * "collections": [ // List of collections + * { + * "name": "c1", // Collection name + * "dbid": 3, // Collection database ID + * "rnum": 2, // Number of documents in collection + * "indexes": [ // List of collections indexes + * { + * "ptr": "/n", // rfc6901 JSON pointer to indexed field + * "mode": 8, // Index mode. Here is EJDB_IDX_I64 + * "idbf": 96, // Index flags. See iwdb_flags_t + * "dbid": 4, // Index database ID + * "rnum": 2 // Number records stored in index database + * } + * ] + * } + * ] + * } + * @endcode + * + * @param db Database handle. Not zero. + * @param [out] jblp JSON object describing ejdb storage. + * Must be disposed by `jbl_destroy()` + */ +IW_EXPORT iwrc ejdb_get_meta(EJDB db, JBL *jblp); + +/** + * Creates an online database backup image and copies it into the specified `target_file`. + * During online backup phase read/write database operations are allowed and not + * blocked for significant amount of time. Backup finish time is placed into `ts` + * as number of milliseconds since epoch. + * + * Online backup guaranties what all records before `ts` timestamp will + * be stored in backup image. Later, online backup image can be + * opened as ordinary database file. + * + * @note In order to avoid deadlocks: close all opened database cursors + * before calling this method or do call in separate thread. + * + * @param Database handle. Not zero. + * @param [out] ts Backup completion timestamp + * @param target_file backup file path + */ +IW_EXPORT iwrc ejdb_online_backup(EJDB db, uint64_t *ts, const char *target_file); + +/** + * @brief Get access to underlying IWKV storage. + * Use it with caution. + * + * @param db Database handle. Not zero. + * @param [out] kvp Placeholder for IWKV storage. + */ +IW_EXPORT iwrc ejdb_get_iwkv(EJDB db, IWKV *kvp); + +/** + * @brief Return `\0` terminated ejdb2 source GIT revision hash. + */ +IW_EXPORT const char *ejdb_git_revision(void); + +/** + * @brief Return `\0` terminated EJDB version string. + */ +IW_EXPORT const char *ejdb_version_full(void); + +/** + * @brief Return major library version. + */ +IW_EXPORT unsigned int ejdb_version_major(void); + +/** + * @brief Return minor library version. + */ +IW_EXPORT unsigned int ejdb_version_minor(void); + +/** + * @brief Return patch library version. + */ +IW_EXPORT unsigned int ejdb_version_patch(void); + +IW_EXTERN_C_END +#endif diff --git a/src/ejdb2_internal.h b/src/ejdb2_internal.h new file mode 100644 index 0000000..eb495c0 --- /dev/null +++ b/src/ejdb2_internal.h @@ -0,0 +1,231 @@ +#pragma once +#ifndef EJDB2_INTERNAL_H +#define EJDB2_INTERNAL_H + +/************************************************************************************************** + * EJDB2 + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +#include "ejdb2.h" +#include "jql.h" +#ifdef JB_HTTP +#include "jbr.h" +#endif +#include "jql_internal.h" +#include "jbl_internal.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "khash.h" +#include "ejdb2cfg.h" + +static_assert(JBNUMBUF_SIZE >= IWFTOA_BUFSIZE, "JBNUMBUF_SIZE >= IWFTOA_BUFSIZE"); + +#define METADB_ID 1 +#define NUMRECSDB_ID 2 // DB for number of records per index/collection +#define KEY_PREFIX_COLLMETA "c." // Full key format: c. +#define KEY_PREFIX_IDXMETA "i." // Full key format: i.. + +#define ENSURE_OPEN(db_) \ + if (!(db_) || !((db_)->open)) { \ + iwlog_error2("Database is not open"); \ + return IW_ERROR_INVALID_STATE; \ + } + +#define API_RLOCK(db_, rci_) \ + ENSURE_OPEN(db_); \ + rci_ = pthread_rwlock_rdlock(&(db_)->rwl); \ + if (rci_) return iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci_) + +#define API_WLOCK(db_, rci_) \ + ENSURE_OPEN(db_); \ + rci_ = pthread_rwlock_wrlock(&(db_)->rwl); \ + if (rci_) return iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci_) + +#define API_WLOCK2(db_, rci_) \ + rci_ = pthread_rwlock_wrlock(&(db_)->rwl); \ + if (rci_) return iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci_) + +#define API_UNLOCK(db_, rci_, rc_) \ + rci_ = pthread_rwlock_unlock(&(db_)->rwl); \ + if (rci_) IWRC(iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci_), rc_) + +#define API_COLL_UNLOCK(jbc_, rci_, rc_) \ + do { \ + rci_ = pthread_rwlock_unlock(&(jbc_)->rwl); \ + if (rci_) IWRC(iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci_), rc_); \ + API_UNLOCK((jbc_)->db, rci_, rc_); \ + } while (0) + +struct _JBIDX; +typedef struct _JBIDX*JBIDX; + +/** Database collection */ +typedef struct _JBCOLL { + uint32_t dbid; /**< IWKV collection database ID */ + const char *name; /**< Collection name */ + IWDB cdb; /**< IWKV collection database */ + EJDB db; /**< Main database reference */ + JBL meta; /**< Collection meta object */ + JBIDX idx; /**< First index in chain */ + int64_t rnum; /**< Number of records stored in collection */ + pthread_rwlock_t rwl; + int64_t id_seq; +} *JBCOLL; + +/** Database collection index */ +struct _JBIDX { + struct _JBIDX *next; /**< Next index in chain */ + int64_t rnum; /**< Number of records stored in index */ + JBCOLL jbc; /**< Owner document collection */ + JBL_PTR ptr; /**< Indexed JSON path poiner 0*/ + IWDB idb; /**< KV database for this index */ + uint32_t dbid; /**< IWKV collection database ID */ + ejdb_idx_mode_t mode; /**< Index mode/type mask */ + iwdb_flags_t idbf; /**< Index database flags */ +}; + +/** Pair: collection name, document id */ +struct _JBDOCREF { + int64_t id; + const char *coll; +}; + +// -V:KHASH_MAP_INIT_STR:522 +KHASH_MAP_INIT_STR(JBCOLLM, JBCOLL) + +struct _EJDB { + IWKV iwkv; + IWDB metadb; + IWDB nrecdb; +#ifdef JB_HTTP + JBR jbr; +#endif + khash_t(JBCOLLM) * mcolls; + iwkv_openflags oflags; + pthread_rwlock_t rwl; /**< Main RWL */ + struct _EJDB_OPTS opts; + volatile bool open; +}; + +struct _JBPHCTX { + int64_t id; + JBCOLL jbc; + JBL jbl; + IWKV_val oldval; +}; + +struct _JBEXEC; + +typedef iwrc (*JB_SCAN_CONSUMER)( + struct _JBEXEC *ctx, IWKV_cursor cur, int64_t id, + int64_t *step, bool *matched, iwrc err); + +/** + * @brief Index can sorter consumer context + */ +struct _JBSSC { + iwrc rc; /**< RC code used for in `_jb_do_sorting` */ + uint32_t *refs; /**< Document references array */ + uint32_t refs_asz; /**< Document references array allocated size */ + uint32_t refs_num; /**< Document references array elements count */ + uint32_t docs_asz; /**< Documents array allocated size */ + uint8_t *docs; /**< Documents byte array */ + uint32_t docs_npos; /**< Next document offset */ + jmp_buf fatal_jmp; + IWFS_EXT sof; /**< Sort overflow file */ + bool sof_active; +}; + +struct _JBMIDX { + JBIDX idx; /**< Index matched this filter */ + JQP_FILTER *filter; /**< Query filter */ + JQP_EXPR *nexpr; /**< Filter node expression */ + JQP_EXPR *expr1; /**< Start index expression (optional) */ + JQP_EXPR *expr2; /**< End index expression (optional) */ + IWKV_cursor_op cursor_init; /**< Initial index cursor position (optional) */ + IWKV_cursor_op cursor_step; /**< Next index cursor step */ + bool orderby_support; /**< Index supported first order-by clause */ +}; + +typedef struct _JBEXEC { + EJDB_EXEC *ux; /**< User defined context */ + JBCOLL jbc; /**< Collection */ + + int64_t istep; + iwrc (*scanner)(struct _JBEXEC *ctx, JB_SCAN_CONSUMER consumer); + uint8_t *jblbuf; /**< Buffer used to keep currently processed document */ + size_t jblbufsz; /**< Size of jblbuf allocated memory */ + bool sorting; /**< Resultset sorting needed */ + IWKV_cursor_op cursor_init; /**< Initial index cursor position (optional) */ + IWKV_cursor_op cursor_step; /**< Next index cursor step */ + struct _JBMIDX midx; /**< Index matching context */ + struct _JBSSC ssc; /**< Result set sorting context */ + + // JQL joned nodes cache + IWSTREE *proj_joined_nodes_cache; + IWPOOL *proj_joined_nodes_pool; +} JBEXEC; + + +typedef uint8_t jb_coll_acquire_t; +#define JB_COLL_ACQUIRE_WRITE ((jb_coll_acquire_t) 0x01U) +#define JB_COLL_ACQUIRE_EXISTING ((jb_coll_acquire_t) 0x02U) + +// Index selector empiric constants +#define JB_IDX_EMPIRIC_MAX_INOP_ARRAY_SIZE 500 +#define JB_IDX_EMPIRIC_MIN_INOP_ARRAY_SIZE 10 +#define JB_IDX_EMPIRIC_MAX_INOP_ARRAY_RATIO 200 + +void jbi_jbl_fill_ikey(JBIDX idx, JBL jbv, IWKV_val *ikey, char numbuf[static JBNUMBUF_SIZE]); +void jbi_jqval_fill_ikey(JBIDX idx, const JQVAL *jqval, IWKV_val *ikey, char numbuf[static JBNUMBUF_SIZE]); +void jbi_node_fill_ikey(JBIDX idx, JBL_NODE node, IWKV_val *ikey, char numbuf[static JBNUMBUF_SIZE]); + +iwrc jbi_consumer(struct _JBEXEC *ctx, IWKV_cursor cur, int64_t id, int64_t *step, bool *matched, iwrc err); +iwrc jbi_sorter_consumer(struct _JBEXEC *ctx, IWKV_cursor cur, int64_t id, int64_t *step, bool *matched, iwrc err); +iwrc jbi_full_scanner(struct _JBEXEC *ctx, JB_SCAN_CONSUMER consumer); +iwrc jbi_selection(JBEXEC *ctx); +iwrc jbi_pk_scanner(struct _JBEXEC *ctx, JB_SCAN_CONSUMER consumer); +iwrc jbi_uniq_scanner(struct _JBEXEC *ctx, JB_SCAN_CONSUMER consumer); +iwrc jbi_dup_scanner(struct _JBEXEC *ctx, JB_SCAN_CONSUMER consumer); +bool jbi_node_expr_matched(JQP_AUX *aux, JBIDX idx, IWKV_cursor cur, JQP_EXPR *expr, iwrc *rcp); + +iwrc jb_get(EJDB db, const char *coll, int64_t id, jb_coll_acquire_t acm, JBL *jblp); +iwrc jb_put(JBCOLL jbc, JBL jbl, int64_t id); +iwrc jb_del(JBCOLL jbc, JBL jbl, int64_t id); +iwrc jb_cursor_set(JBCOLL jbc, IWKV_cursor cur, int64_t id, JBL jbl); +iwrc jb_cursor_del(JBCOLL jbc, IWKV_cursor cur, int64_t id, JBL jbl); + +iwrc jb_collection_join_resolver(int64_t id, const char *coll, JBL *out, JBEXEC *ctx); +int jb_proj_node_cache_cmp(const void *v1, const void *v2); +void jb_proj_node_kvfree(void *key, void *val); + +#endif diff --git a/src/ejdb2cfg.h b/src/ejdb2cfg.h new file mode 100644 index 0000000..f4f3ff2 --- /dev/null +++ b/src/ejdb2cfg.h @@ -0,0 +1,57 @@ +#pragma once +#ifndef JBCFG_H +#define JBCFG_H + +/************************************************************************************************** + * EJDB2 + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +#if !defined(IW_32) && !defined(IW_64) +#error Unknown CPU bits +#endif + +#define EJDB2_GIT_REVISION "" +#define EJDB2_VERSION "2.0.59" +#define EJDB2_VERSION_MAJOR 2 +#define EJDB2_VERSION_MINOR 0 +#define EJDB2_VERSION_PATCH 59 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define JBNUMBUF_SIZE 64 + +#ifndef static_assert +#define static_assert _Static_assert +#endif + +#endif diff --git a/src/examples/CMakeLists.txt b/src/examples/CMakeLists.txt new file mode 100644 index 0000000..ea4c91b --- /dev/null +++ b/src/examples/CMakeLists.txt @@ -0,0 +1,5 @@ +link_libraries(ejdb2_s) +foreach (EX IN ITEMS example1) + add_executable(${EX} ${EX}.c) + set_target_properties(${EX} PROPERTIES COMPILE_FLAGS "-DIW_STATIC") +endforeach () diff --git a/src/examples/example1.c b/src/examples/example1.c new file mode 100644 index 0000000..91181f8 --- /dev/null +++ b/src/examples/example1.c @@ -0,0 +1,76 @@ +/// Sample records put/query example +#include + +#define CHECK(rc_) \ + if (rc_) { \ + iwlog_ecode_error3(rc_); \ + return 1; \ + } + +static iwrc documents_visitor(EJDB_EXEC *ctx, const EJDB_DOC doc, int64_t *step) { + // Print document to stderr + return jbl_as_json(doc->raw, jbl_fstream_json_printer, stderr, JBL_PRINT_PRETTY); +} + +int main() { + + EJDB_OPTS opts = { + .kv = { + .path = "example.db", + .oflags = IWKV_TRUNC + } + }; + EJDB db; // EJDB2 storage handle + int64_t id; // Document id placeholder + JQL q = 0; // Query instance + JBL jbl = 0; // Json document + + iwrc rc = ejdb_init(); + CHECK(rc); + + rc = ejdb_open(&opts, &db); + CHECK(rc); + + // First record + rc = jbl_from_json(&jbl, "{\"name\":\"Bianca\", \"age\":4}"); + RCGO(rc, finish); + rc = ejdb_put_new(db, "parrots", jbl, &id); + RCGO(rc, finish); + jbl_destroy(&jbl); + + // Second record + rc = jbl_from_json(&jbl, "{\"name\":\"Darko\", \"age\":8}"); + RCGO(rc, finish); + rc = ejdb_put_new(db, "parrots", jbl, &id); + RCGO(rc, finish); + jbl_destroy(&jbl); + + // Now execute a query + rc = jql_create(&q, "parrots", "/[age > :age]"); + RCGO(rc, finish); + + EJDB_EXEC ux = { + .db = db, + .q = q, + .visitor = documents_visitor + }; + + // Set query placeholder value. + // Actual query will be /[age > 3] + rc = jql_set_i64(q, "age", 0, 3); + RCGO(rc, finish); + + // Now execute the query + rc = ejdb_exec(&ux); + +finish: + if (q) { + jql_destroy(&q); + } + if (jbl) { + jbl_destroy(&jbl); + } + ejdb_close(&db); + CHECK(rc); + return 0; +} diff --git a/src/jbi/jbi_consumer.c b/src/jbi/jbi_consumer.c new file mode 100644 index 0000000..4b2d070 --- /dev/null +++ b/src/jbi/jbi_consumer.c @@ -0,0 +1,138 @@ +#include "ejdb2_internal.h" + +iwrc jbi_consumer(struct _JBEXEC *ctx, IWKV_cursor cur, int64_t id, int64_t *step, bool *matched, iwrc err) { + if (!id) { // EOF scan + return err; + } + + iwrc rc; + struct _JBL jbl; + size_t vsz = 0; + EJDB_EXEC *ux = ctx->ux; + IWPOOL *pool = ux->pool; + +start: + { + if (cur) { + rc = iwkv_cursor_copy_val(cur, ctx->jblbuf, ctx->jblbufsz, &vsz); + } else { + IWKV_val key = { + .data = &id, + .size = sizeof(id) + }; + rc = iwkv_get_copy(ctx->jbc->cdb, &key, ctx->jblbuf, ctx->jblbufsz, &vsz); + } + if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + if (ctx->midx.idx) { + iwlog_error("Orphaned index entry." + "\n\tCollection db: %" PRIu32 + "\n\tIndex db: %" PRIu32 + "\n\tEntry id: %" PRId64, ctx->jbc->dbid, ctx->midx.idx->dbid, id); + } else { + iwlog_error("Orphaned index entry." + "\n\tCollection db: %" PRIu32 + "\n\tEntry id: %" PRId64, ctx->jbc->dbid, id); + } + goto finish; + } + RCGO(rc, finish); + if (vsz > ctx->jblbufsz) { + size_t nsize = MAX(vsz, ctx->jblbufsz * 2); + void *nbuf = realloc(ctx->jblbuf, nsize); + if (!nbuf) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + ctx->jblbuf = nbuf; + ctx->jblbufsz = nsize; + goto start; + } + } + + rc = jbl_from_buf_keep_onstack(&jbl, ctx->jblbuf, vsz); + RCGO(rc, finish); + + rc = jql_matched(ux->q, &jbl, matched); + if (rc || !*matched || (ux->skip && (ux->skip-- > 0))) { + goto finish; + } + if (ctx->istep > 0) { + --ctx->istep; + } else if (ctx->istep < 0) { + ++ctx->istep; + } + if (!ctx->istep) { + JQL q = ux->q; + ctx->istep = 1; + struct JQP_AUX *aux = q->aux; + struct _EJDB_DOC doc = { + .id = id, + .raw = &jbl + }; + if (aux->apply || aux->apply_placeholder || aux->projection) { + JBL_NODE root; + if (!pool) { + pool = iwpool_create(jbl.bn.size * 2); + if (!pool) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + } + rc = jbl_to_node(&jbl, &root, true, pool); + RCGO(rc, finish); + doc.node = root; + if (aux->qmode & JQP_QRY_APPLY_DEL) { + if (cur) { + rc = jb_cursor_del(ctx->jbc, cur, id, &jbl); + } else { + rc = jb_del(ctx->jbc, &jbl, id); + } + } else if (aux->apply || aux->apply_placeholder) { + struct _JBL sn = { 0 }; + rc = jql_apply(q, root, pool); + RCGO(rc, finish); + rc = _jbl_from_node(&sn, root); + RCGO(rc, finish); + if (cur) { + rc = jb_cursor_set(ctx->jbc, cur, id, &sn); + } else { + rc = jb_put(ctx->jbc, &sn, id); + } + binn_free(&sn.bn); + } + RCGO(rc, finish); + if (aux->projection) { + rc = jql_project(q, root, pool, ctx); + RCGO(rc, finish); + } + } else if (aux->qmode & JQP_QRY_APPLY_DEL) { + if (cur) { + rc = jb_cursor_del(ctx->jbc, cur, id, &jbl); + } else { + rc = jb_del(ctx->jbc, &jbl, id); + } + RCGO(rc, finish); + } + if (!(aux->qmode & JQP_QRY_AGGREGATE)) { + do { + ctx->istep = 1; + rc = ux->visitor(ux, &doc, &ctx->istep); + RCGO(rc, finish); + } while (ctx->istep == -1); + } + ++ux->cnt; + *step = ctx->istep > 0 ? 1 : ctx->istep < 0 ? -1 : 0; + if (--ux->limit < 1) { + *step = 0; + } + } else { + *step = ctx->istep > 0 ? 1 : ctx->istep < 0 ? -1 : 0; // -V547 + } + +finish: + if (pool && (pool != ctx->ux->pool)) { + iwpool_destroy(pool); + } + return rc; +} diff --git a/src/jbi/jbi_dup_scanner.c b/src/jbi/jbi_dup_scanner.c new file mode 100644 index 0000000..309dcca --- /dev/null +++ b/src/jbi/jbi_dup_scanner.c @@ -0,0 +1,302 @@ +#include "ejdb2_internal.h" + +static iwrc _jbi_consume_eq(struct _JBEXEC *ctx, JQVAL *jqval, JB_SCAN_CONSUMER consumer) { + iwrc rc; + bool matched; + IWKV_cursor cur; + char numbuf[JBNUMBUF_SIZE]; + + int64_t step = 1; + struct _JBMIDX *midx = &ctx->midx; + JBIDX idx = midx->idx; + IWKV_cursor_op cursor_reverse_step = IWKV_CURSOR_NEXT; + midx->cursor_step = IWKV_CURSOR_PREV; + + IWKV_val key; + jbi_jqval_fill_ikey(idx, jqval, &key, numbuf); + key.compound = INT64_MIN; + if (!key.size) { + return consumer(ctx, 0, 0, 0, 0, 0); + } + rc = iwkv_cursor_open(idx->idb, &cur, IWKV_CURSOR_GE, &key); + if (rc == IWKV_ERROR_NOTFOUND) { + return consumer(ctx, 0, 0, 0, 0, 0); + } else { + RCRET(rc); + } + + do { + if (step > 0) { + --step; + } else if (step < 0) { + ++step; + } + if (!step) { + int64_t id; + rc = iwkv_cursor_is_matched_key(cur, &key, &matched, &id); + RCGO(rc, finish); + if (!matched) { + break; + } + step = 1; + rc = consumer(ctx, 0, id, &step, &matched, 0); + RCGO(rc, finish); + } + } while (step && !(rc = iwkv_cursor_to(cur, step > 0 ? midx->cursor_step : cursor_reverse_step))); + +finish: + if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + } + if (cur) { + iwkv_cursor_close(&cur); + } + return consumer(ctx, 0, 0, 0, 0, rc); +} + +static int _jbi_cmp_jqval(const void *v1, const void *v2) { + iwrc rc; + const JQVAL *jqv1 = v1; + const JQVAL *jqv2 = v2; + return jql_cmp_jqval_pair(jqv1, jqv2, &rc); +} + +static iwrc _jbi_consume_in_node(struct _JBEXEC *ctx, JQVAL *jqval, JB_SCAN_CONSUMER consumer) { + int i; + int64_t id; + bool matched; + char jqvarrbuf[512]; + char numbuf[JBNUMBUF_SIZE]; + + iwrc rc = 0; + int64_t step = 1; + IWKV_cursor cur = 0; + struct _JBMIDX *midx = &ctx->midx; + JBIDX idx = midx->idx; + IWKV_val key = { .compound = INT64_MIN }; + JBL_NODE nv = jqval->vnode->child; + + for (i = 0; nv; nv = nv->next) { + if ((nv->type >= JBV_BOOL) && (nv->type <= JBV_STR)) { + ++i; + } + } + if (i == 0) { + return consumer(ctx, 0, 0, 0, 0, 0); + } + + JQVAL *jqvarr = (i * sizeof(*jqvarr)) <= sizeof(jqvarrbuf) + ? jqvarrbuf : malloc(i * sizeof(*jqvarr)); + if (!jqvarr) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + for (i = 0, nv = jqval->vnode->child; nv; nv = nv->next) { + if ((nv->type >= JBV_BOOL) && (nv->type <= JBV_STR)) { + JQVAL jqv; + jql_node_to_jqval(nv, &jqv); + memcpy(&jqvarr[i++], &jqv, sizeof(jqv)); + } + } + // Sort jqvarr according to index order, lowest first (asc) + qsort(jqvarr, i, sizeof(jqvarr[0]), _jbi_cmp_jqval); + + for (int c = 0; c < i && !rc; ++c) { + JQVAL *jqv = &jqvarr[c]; + jbi_jqval_fill_ikey(idx, jqv, &key, numbuf); + if (cur) { + iwkv_cursor_close(&cur); + } + rc = iwkv_cursor_open(idx->idb, &cur, IWKV_CURSOR_GE, &key); + RCGO(rc, finish); + do { + if (step > 0) { + --step; + } else if (step < 0) { + ++step; + } + if (!step) { + rc = iwkv_cursor_is_matched_key(cur, &key, &matched, &id); + RCGO(rc, finish); + if (!matched) { + break; + } + step = 1; + rc = consumer(ctx, 0, id, &step, &matched, 0); + RCGO(rc, finish); + } + } while (step && !(rc = iwkv_cursor_to(cur, IWKV_CURSOR_PREV))); // !!! only one direction + } + +finish: + if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + } + if (cur) { + iwkv_cursor_close(&cur); + } + if ((char*) jqvarr != jqvarrbuf) { + free(jqvarr); + } + return consumer(ctx, 0, 0, 0, 0, rc); +} + +static iwrc _jbi_consume_scan(struct _JBEXEC *ctx, JQVAL *jqval, JB_SCAN_CONSUMER consumer) { + size_t sz; + IWKV_cursor cur; + char numbuf[JBNUMBUF_SIZE]; + + int64_t step = 1, prev_id = 0; + struct _JBMIDX *midx = &ctx->midx; + JBIDX idx = midx->idx; + jqp_op_t expr1_op = midx->expr1->op->value; + + IWKV_val key; + jbi_jqval_fill_ikey(idx, jqval, &key, numbuf); + if (!key.size) { + return consumer(ctx, 0, 0, 0, 0, 0); + } + key.compound = (midx->cursor_step == IWKV_CURSOR_PREV) ? INT64_MIN : INT64_MAX; + + iwrc rc = iwkv_cursor_open(idx->idb, &cur, midx->cursor_init, &key); + if ((rc == IWKV_ERROR_NOTFOUND) && ((expr1_op == JQP_OP_LT) || (expr1_op == JQP_OP_LTE))) { + iwkv_cursor_close(&cur); + key.compound = INT64_MAX; + midx->cursor_init = IWKV_CURSOR_BEFORE_FIRST; + midx->cursor_step = IWKV_CURSOR_NEXT; + rc = iwkv_cursor_open(idx->idb, &cur, midx->cursor_init, 0); + RCGO(rc, finish); + if (!midx->expr2) { // Fail fast + midx->expr2 = midx->expr1; + } + } else if (rc) { + goto finish; + } + + if (midx->cursor_init < IWKV_CURSOR_NEXT) { // IWKV_CURSOR_BEFORE_FIRST || IWKV_CURSOR_AFTER_LAST + rc = iwkv_cursor_to(cur, midx->cursor_step); + RCGO(rc, finish); + } + + IWKV_cursor_op cursor_reverse_step = (midx->cursor_step == IWKV_CURSOR_PREV) + ? IWKV_CURSOR_NEXT : IWKV_CURSOR_PREV; + do { + if (step > 0) { + --step; + } else if (step < 0) { + ++step; + } + if (!step) { + int64_t id; + bool matched = false; + rc = iwkv_cursor_copy_key(cur, 0, 0, &sz, &id); + RCGO(rc, finish); + if ( midx->expr2 + && !midx->expr2->prematched + && !jbi_node_expr_matched(ctx->ux->q->aux, midx->idx, cur, midx->expr2, &rc)) { + break; + } + if ( (expr1_op == JQP_OP_PREFIX) + && !jbi_node_expr_matched(ctx->ux->q->aux, midx->idx, cur, midx->expr1, &rc)) { + break; + } + RCGO(rc, finish); + step = 1; + if (id != prev_id) { + rc = consumer(ctx, 0, id, &step, &matched, 0); + RCGO(rc, finish); + if (!midx->expr1->prematched && matched && (expr1_op != JQP_OP_PREFIX)) { + // Further scan will always match main index expression + midx->expr1->prematched = true; + } + prev_id = step < 1 ? 0 : id; + } + } + } while (step && !(rc = iwkv_cursor_to(cur, step > 0 ? midx->cursor_step : cursor_reverse_step))); + +finish: + if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + } + if (cur) { + iwkv_cursor_close(&cur); + } + return consumer(ctx, 0, 0, 0, 0, rc); +} + +static iwrc _jbi_consume_noxpr_scan(struct _JBEXEC *ctx, JB_SCAN_CONSUMER consumer) { + size_t sz; + IWKV_cursor cur; + int64_t step = 1, prev_id = 0; + struct _JBMIDX *midx = &ctx->midx; + IWKV_cursor_op cursor_reverse_step = (midx->cursor_step == IWKV_CURSOR_PREV) + ? IWKV_CURSOR_NEXT : IWKV_CURSOR_PREV; + + iwrc rc = iwkv_cursor_open(midx->idx->idb, &cur, midx->cursor_init, 0); + RCGO(rc, finish); + if (midx->cursor_init < IWKV_CURSOR_NEXT) { // IWKV_CURSOR_BEFORE_FIRST || IWKV_CURSOR_AFTER_LAST + rc = iwkv_cursor_to(cur, midx->cursor_step); + RCGO(rc, finish); + } + do { + if (step > 0) { + --step; + } else if (step < 0) { + ++step; + } + if (!step) { + int64_t id; + bool matched; + rc = iwkv_cursor_copy_key(cur, 0, 0, &sz, &id); + RCGO(rc, finish); + step = 1; + if (id != prev_id) { + rc = consumer(ctx, 0, id, &step, &matched, 0); + RCGO(rc, finish); + prev_id = step < 1 ? 0 : id; + } + } + } while (step && !(rc = iwkv_cursor_to(cur, step > 0 ? midx->cursor_step : cursor_reverse_step))); + +finish: + if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + } + if (cur) { + iwkv_cursor_close(&cur); + } + return consumer(ctx, 0, 0, 0, 0, rc); +} + +iwrc jbi_dup_scanner(struct _JBEXEC *ctx, JB_SCAN_CONSUMER consumer) { + iwrc rc; + struct _JBMIDX *midx = &ctx->midx; + if (!midx->expr1) { + return _jbi_consume_noxpr_scan(ctx, consumer); + } + JQP_QUERY *qp = ctx->ux->q->qp; + JQVAL *jqval = jql_unit_to_jqval(qp->aux, midx->expr1->right, &rc); + RCRET(rc); + switch (midx->expr1->op->value) { + case JQP_OP_EQ: + return _jbi_consume_eq(ctx, jqval, consumer); + case JQP_OP_IN: + if (jqval->type == JQVAL_JBLNODE) { + return _jbi_consume_in_node(ctx, jqval, consumer); + } else { + iwlog_ecode_error3(IW_ERROR_ASSERTION); + return IW_ERROR_ASSERTION; + } + break; + default: + break; + } + + if ((midx->expr1->op->value == JQP_OP_GT) && (jqval->type == JQVAL_I64)) { + JQVAL mjqv; + memcpy(&mjqv, jqval, sizeof(*jqval)); + mjqv.vi64 = mjqv.vi64 + 1; // Because for index scan we use `IWKV_CURSOR_GE` + return _jbi_consume_scan(ctx, &mjqv, consumer); + } else { + return _jbi_consume_scan(ctx, jqval, consumer); + } +} diff --git a/src/jbi/jbi_full_scanner.c b/src/jbi/jbi_full_scanner.c new file mode 100644 index 0000000..7e93a52 --- /dev/null +++ b/src/jbi/jbi_full_scanner.c @@ -0,0 +1,40 @@ +#include "ejdb2_internal.h" + +iwrc jbi_full_scanner(struct _JBEXEC *ctx, JB_SCAN_CONSUMER consumer) { + bool matched; + IWKV_cursor cur; + int64_t step = 1; + iwrc rc = iwkv_cursor_open(ctx->jbc->cdb, &cur, ctx->cursor_init, 0); + RCRET(rc); + + IWKV_cursor_op cursor_reverse_step = (ctx->cursor_step == IWKV_CURSOR_NEXT) + ? IWKV_CURSOR_PREV : IWKV_CURSOR_NEXT; + + while (step && !(rc = iwkv_cursor_to(cur, step > 0 ? ctx->cursor_step : cursor_reverse_step))) { + if (step > 0) { + --step; + } else if (step < 0) { + ++step; // -V547 + } + if (!step) { + size_t sz; + int64_t id; + rc = iwkv_cursor_copy_key(cur, &id, sizeof(id), &sz, 0); + RCBREAK(rc); + if (sz != sizeof(id)) { + rc = IWKV_ERROR_CORRUPTED; + iwlog_ecode_error3(rc); + break; + } + step = 1; + matched = false; + rc = consumer(ctx, cur, id, &step, &matched, 0); + RCBREAK(rc); + } + } + if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + } + iwkv_cursor_close(&cur); + return consumer(ctx, 0, 0, 0, 0, rc); +} diff --git a/src/jbi/jbi_pk_scanner.c b/src/jbi/jbi_pk_scanner.c new file mode 100644 index 0000000..b7a9bca --- /dev/null +++ b/src/jbi/jbi_pk_scanner.c @@ -0,0 +1,43 @@ +#include "ejdb2_internal.h" + +// Primary key scanner +iwrc jbi_pk_scanner(struct _JBEXEC *ctx, JB_SCAN_CONSUMER consumer) { + iwrc rc = 0; + int64_t id, step; + bool matched; + struct JQP_AUX *aux = ctx->ux->q->aux; + assert(aux->expr->flags & JQP_EXPR_NODE_FLAG_PK); + JQP_EXPR_NODE_PK *pk = (void*) aux->expr; + assert(pk->argument); + JQVAL *jqvp = jql_unit_to_jqval(aux, pk->argument, &rc); + RCGO(rc, finish); + + if ((jqvp->type == JQVAL_JBLNODE) && (jqvp->vnode->type == JBV_ARRAY)) { + JQVAL jqv; + JBL_NODE nv = jqvp->vnode->child; + if (!nv) { + goto finish; + } + step = 1; + do { + jql_node_to_jqval(nv, &jqv); + if (jql_jqval_as_int(&jqv, &id)) { + if (step > 0) { + --step; + } else if (step < 0) { + ++step; + } + if (!step) { + step = 1; + rc = consumer(ctx, 0, id, &step, &matched, 0); + RCGO(rc, finish); + } + } + } while (step && (step > 0 ? (nv = nv->next) : (nv = nv->prev))); + } else if (jql_jqval_as_int(jqvp, &id)) { + rc = consumer(ctx, 0, id, &step, &matched, 0); + } + +finish: + return consumer(ctx, 0, 0, 0, 0, rc); +} diff --git a/src/jbi/jbi_selection.c b/src/jbi/jbi_selection.c new file mode 100644 index 0000000..83d2415 --- /dev/null +++ b/src/jbi/jbi_selection.c @@ -0,0 +1,465 @@ +#include "ejdb2_internal.h" + +#define JB_SOLID_EXPRNUM 127 + +static void _jbi_print_index(struct _JBIDX *idx, IWXSTR *xstr) { + int cnt = 0; + ejdb_idx_mode_t m = idx->mode; + if (m & EJDB_IDX_UNIQUE) { + cnt++; + iwxstr_cat2(xstr, "UNIQUE"); + } + if (m & EJDB_IDX_STR) { + if (cnt++) { + iwxstr_cat2(xstr, "|"); + } + iwxstr_cat2(xstr, "STR"); + } + if (m & EJDB_IDX_I64) { + if (cnt++) { + iwxstr_cat2(xstr, "|"); + } + iwxstr_cat2(xstr, "I64"); + } + if (m & EJDB_IDX_F64) { + if (cnt++) { + iwxstr_cat2(xstr, "|"); + } + iwxstr_cat2(xstr, "F64"); + } + if (cnt++) { + iwxstr_cat2(xstr, "|"); + } + iwxstr_printf(xstr, "%lld ", idx->rnum); + jbl_ptr_serialize(idx->ptr, xstr); +} + +static void _jbi_log_cursor_op(IWXSTR *xstr, IWKV_cursor_op op) { + switch (op) { + case IWKV_CURSOR_EQ: + iwxstr_cat2(xstr, "IWKV_CURSOR_EQ"); + break; + case IWKV_CURSOR_GE: + iwxstr_cat2(xstr, "IWKV_CURSOR_GE"); + break; + case IWKV_CURSOR_NEXT: + iwxstr_cat2(xstr, "IWKV_CURSOR_NEXT"); + break; + case IWKV_CURSOR_PREV: + iwxstr_cat2(xstr, "IWKV_CURSOR_PREV"); + break; + case IWKV_CURSOR_BEFORE_FIRST: + iwxstr_cat2(xstr, "IWKV_CURSOR_BEFORE_FIRST"); + break; + case IWKV_CURSOR_AFTER_LAST: + iwxstr_cat2(xstr, "IWKV_CURSOR_AFTER_LAST"); + break; + } +} + +static void _jbi_log_index_rules(IWXSTR *xstr, struct _JBMIDX *mctx) { + _jbi_print_index(mctx->idx, xstr); + if (mctx->expr1) { + iwxstr_cat2(xstr, " EXPR1: \'"); + jqp_print_filter_node_expr(mctx->expr1, jbl_xstr_json_printer, xstr); + iwxstr_cat2(xstr, "\'"); + } + if (mctx->expr2) { + iwxstr_cat2(xstr, " EXPR2: \'"); + jqp_print_filter_node_expr(mctx->expr2, jbl_xstr_json_printer, xstr); + iwxstr_cat2(xstr, "\'"); + } + if (mctx->cursor_init) { + iwxstr_cat2(xstr, " INIT: "); + _jbi_log_cursor_op(xstr, mctx->cursor_init); + } + if (mctx->cursor_step) { + iwxstr_cat2(xstr, " STEP: "); + _jbi_log_cursor_op(xstr, mctx->cursor_step); + } + if (mctx->orderby_support) { + iwxstr_cat2(xstr, " ORDERBY"); + } + iwxstr_cat2(xstr, "\n"); +} + +IW_INLINE int _jbi_idx_expr_op_weight(struct _JBMIDX *midx) { + jqp_op_t op = midx->expr1->op->value; + switch (op) { + case JQP_OP_EQ: + return 10; + case JQP_OP_IN: + //case JQP_OP_NI: todo + return 9; + default: + break; + } + if (midx->orderby_support) { + return 8; + } + switch (op) { + case JQP_OP_GT: + case JQP_OP_GTE: + return 7; + case JQP_OP_PREFIX: + return 6; + case JQP_OP_LT: + case JQP_OP_LTE: + return 5; + default: + return 0; + } +} + +static bool _jbi_is_solid_node_expression(const JQP_NODE *n) { + JQPUNIT *unit = n->value; + for (const JQP_EXPR *expr = &unit->expr; expr; expr = expr->next) { + if ( expr->op->negate + || (expr->join && (expr->join->negate || (expr->join->value == JQP_JOIN_OR) )) + || (expr->op->value == JQP_OP_RE) ) { + // No negate conditions, No OR, No regexp + return false; + } + JQPUNIT *left = expr->left; + if ( (left->type == JQP_EXPR_TYPE) + || ((left->type == JQP_STRING_TYPE) && (left->string.flavour & JQP_STR_STAR))) { + return false; + } + } + return true; +} + +static iwrc _jbi_compute_index_rules(JBEXEC *ctx, struct _JBMIDX *mctx) { + JQP_EXPR *expr = mctx->nexpr; // Node expression + if (!expr) { + return 0; + } + JQP_AUX *aux = ctx->ux->q->aux; + + for ( ; expr; expr = expr->next) { + iwrc rc = 0; + jqp_op_t op = expr->op->value; + JQVAL *rv = jql_unit_to_jqval(aux, expr->right, &rc); + RCRET(rc); + if (expr->left->type != JQP_STRING_TYPE) { + continue; + } + switch (rv->type) { + case JQVAL_NULL: + case JQVAL_RE: + case JQVAL_BINN: + continue; + case JQVAL_JBLNODE: { + if ((op != JQP_OP_IN) || (rv->vnode->type != JBV_ARRAY)) { + continue; + } + int vcnt = 0; + for (JBL_NODE n = rv->vnode->child; n; n = n->next, ++vcnt) ; + if ( (vcnt > JB_IDX_EMPIRIC_MIN_INOP_ARRAY_SIZE) + && ( (vcnt > JB_IDX_EMPIRIC_MAX_INOP_ARRAY_SIZE) + || (mctx->idx->rnum < rv->vbinn->count * JB_IDX_EMPIRIC_MAX_INOP_ARRAY_RATIO) )) { + // No index for large IN array | small collection size + continue; + } + break; + } + default: + break; + } + switch (op) { + case JQP_OP_EQ: + mctx->cursor_init = IWKV_CURSOR_EQ; + mctx->expr1 = expr; + mctx->expr2 = 0; + return 0; + case JQP_OP_PREFIX: + if (!(mctx->idx->mode & EJDB_IDX_STR)) { + mctx->expr1 = 0; + return 0; + } + if ((rv->type == JQVAL_STR) && (*rv->vstr == '\0')) { + continue; + } + case JQP_OP_GT: + case JQP_OP_GTE: + if (mctx->cursor_init != IWKV_CURSOR_EQ) { + if (mctx->expr1 && (mctx->cursor_init == IWKV_CURSOR_GE) && (op != JQP_OP_PREFIX)) { + JQVAL *pval = jql_unit_to_jqval(aux, mctx->expr1->right, &rc); + RCRET(rc); + int cv = jql_cmp_jqval_pair(pval, rv, &rc); + RCRET(rc); + if (cv > 0) { + break; + } + } + mctx->expr1 = expr; + mctx->cursor_init = IWKV_CURSOR_GE; + mctx->cursor_step = IWKV_CURSOR_PREV; + } + break; + case JQP_OP_LT: + case JQP_OP_LTE: + if (mctx->expr2) { + JQVAL *pval = jql_unit_to_jqval(aux, mctx->expr2->right, &rc); + RCRET(rc); + int cv = jql_cmp_jqval_pair(pval, rv, &rc); + RCRET(rc); + if (cv < 0) { + break; + } + } + mctx->expr2 = expr; + break; + case JQP_OP_IN: + if ((mctx->cursor_init != IWKV_CURSOR_EQ) && (rv->type >= JQVAL_JBLNODE)) { + mctx->expr1 = expr; + mctx->expr2 = 0; + mctx->cursor_init = IWKV_CURSOR_EQ; + } + break; + default: + continue; + } + } + + if (mctx->expr2) { + if (!mctx->expr1) { + mctx->expr1 = mctx->expr2; + mctx->expr2 = 0; + mctx->cursor_init = IWKV_CURSOR_GE; + mctx->cursor_step = IWKV_CURSOR_NEXT; + } + } + + // Orderby compatibility + if (mctx->orderby_support) { + if ((aux->orderby_num == 1) && (mctx->cursor_init != IWKV_CURSOR_EQ)) { + bool desc = (aux->orderby_ptrs[0]->op & 1) != 0; // Desc sort + if (desc) { + if (mctx->cursor_step != IWKV_CURSOR_NEXT) { + mctx->orderby_support = false; + } + } else { + if (mctx->cursor_step != IWKV_CURSOR_PREV) { + mctx->orderby_support = false; + } + } + if (!mctx->orderby_support && mctx->expr2) { + JQP_EXPR *tmp = mctx->expr1; + mctx->expr1 = mctx->expr2; + mctx->expr2 = tmp; + mctx->orderby_support = true; + if (desc) { + mctx->cursor_step = IWKV_CURSOR_NEXT; + } else { + mctx->cursor_step = IWKV_CURSOR_PREV; + } + } + } else { + mctx->orderby_support = false; + } + } + + return 0; +} + +static iwrc _jbi_collect_indexes( + JBEXEC *ctx, + const struct JQP_EXPR_NODE *en, + struct _JBMIDX marr[static JB_SOLID_EXPRNUM], + size_t *snp) { + + iwrc rc = 0; + if (*snp >= JB_SOLID_EXPRNUM - 1) { + return 0; + } + if (en->type == JQP_EXPR_NODE_TYPE) { + struct JQP_EXPR_NODE *cn = en->chain; + for ( ; cn; cn = cn->next) { + if (cn->join && (cn->join->value == JQP_JOIN_OR)) { + return 0; + } + } + for (cn = en->chain; cn; cn = cn->next) { + if (!cn->join || !cn->join->negate) { + rc = _jbi_collect_indexes(ctx, cn, marr, snp); + RCRET(rc); + } + } + } else if (en->type == JQP_FILTER_TYPE) { + int fnc = 0; + JQP_FILTER *f = (JQP_FILTER*) en; // -V1027 + for (JQP_NODE *n = f->node; n; n = n->next, ++fnc) { + switch (n->ntype) { + case JQP_NODE_ANY: + case JQP_NODE_ANYS: + return 0; + case JQP_NODE_FIELD: + break; + case JQP_NODE_EXPR: + if (!_jbi_is_solid_node_expression(n)) { + return 0; + } + break; + } + } + + struct JQP_AUX *aux = ctx->ux->q->aux; + struct _JBL_PTR *obp = aux->orderby_num ? aux->orderby_ptrs[0] : 0; + + for (struct _JBIDX *idx = ctx->jbc->idx; idx && *snp < JB_SOLID_EXPRNUM; idx = idx->next) { + struct _JBMIDX mctx = { .filter = f }; + struct _JBL_PTR *ptr = idx->ptr; + if (ptr->cnt > fnc) { + continue; + } + + JQP_EXPR *nexpr = 0; + int i = 0, j = 0; + for (JQP_NODE *n = f->node; n && i < ptr->cnt; n = n->next, ++i) { + nexpr = 0; + const char *field = 0; + if (n->ntype == JQP_NODE_FIELD) { + field = n->value->string.value; + } else if (n->ntype == JQP_NODE_EXPR) { + nexpr = &n->value->expr; + JQPUNIT *left = nexpr->left; + if (left->type == JQP_STRING_TYPE) { + field = left->string.value; + } + } + if (!field || (strcmp(field, ptr->n[i]) != 0)) { + break; + } + if (obp && (i == j) && (i < obp->cnt) && !strcmp(ptr->n[i], obp->n[i])) { + j++; + } + // Check for the last iteration and the special `**` case + if ( (i == ptr->cnt - 1) + && (idx->idbf & IWDB_COMPOUND_KEYS) + && n->next && !n->next->next && (n->next->ntype == JQP_NODE_EXPR)) { + JQPUNIT *left = n->next->value->expr.left; + if ((left->type == JQP_STRING_TYPE) && (left->string.flavour & JQP_STR_DBL_STAR)) { + i++; + j++; + nexpr = &n->next->value->expr; + break; + } + } + } + if ((i == ptr->cnt) && nexpr) { + mctx.idx = idx; + mctx.nexpr = nexpr; + mctx.orderby_support = (i == j); + rc = _jbi_compute_index_rules(ctx, &mctx); + RCRET(rc); + if (!mctx.expr1) { // Cannot find matching expressions + continue; + } + if (ctx->ux->log) { + iwxstr_cat2(ctx->ux->log, "[INDEX] MATCHED "); + _jbi_log_index_rules(ctx->ux->log, &mctx); + } + marr[*snp] = mctx; + *snp = *snp + 1; + } + } + } + return rc; +} + +static int _jbi_idx_cmp(const void *o1, const void *o2) { + struct _JBMIDX *d1 = (struct _JBMIDX*) o1; + struct _JBMIDX *d2 = (struct _JBMIDX*) o2; + assert(d1 && d2); + int w1 = _jbi_idx_expr_op_weight(d1); + int w2 = _jbi_idx_expr_op_weight(d2); + if (w2 != w1) { + return w2 - w1; + } + w1 = d1->expr2 != 0; + w2 = d2->expr2 != 0; + if (w2 != w1) { + return w2 - w1; + } + if (d1->idx->rnum != d2->idx->rnum) { + return (d1->idx->rnum - d2->idx->rnum) > 0 ? 1 : -1; + } + return (d1->idx->ptr->cnt - d2->idx->ptr->cnt); +} + +static struct _JBIDX *_jbi_select_index_for_orderby(JBEXEC *ctx) { + struct JQP_AUX *aux = ctx->ux->q->aux; + struct _JBL_PTR *obp = aux->orderby_ptrs[0]; + assert(obp); + for (struct _JBIDX *idx = ctx->jbc->idx; idx; idx = idx->next) { + struct _JBL_PTR *ptr = idx->ptr; + if (obp->cnt != ptr->cnt) { + continue; + } + int i = 0; + for ( ; i < obp->cnt && !strcmp(ptr->n[i], obp->n[i]); ++i) ; + if (i == obp->cnt) { + memset(&ctx->midx, 0, sizeof(ctx->midx)); + if (!(obp->op & 1)) { // Asc sort + ctx->cursor_init = IWKV_CURSOR_AFTER_LAST; + ctx->cursor_step = IWKV_CURSOR_PREV; + } + ctx->midx.idx = idx; + ctx->midx.orderby_support = true; + ctx->midx.cursor_init = ctx->cursor_init; + ctx->midx.cursor_step = ctx->cursor_step; + ctx->sorting = false; + return idx; + } + } + return 0; +} + +iwrc jbi_selection(JBEXEC *ctx) { + iwrc rc = 0; + size_t snp = 0; + struct JQP_AUX *aux = ctx->ux->q->aux; + struct _JBMIDX fctx[JB_SOLID_EXPRNUM] = { 0 }; + + ctx->cursor_init = IWKV_CURSOR_BEFORE_FIRST; + ctx->cursor_step = IWKV_CURSOR_NEXT; + + // Index not found: + if (aux->orderby_num) { + ctx->sorting = true; + } else if (aux->qmode & JQP_QRY_INVERSE) { + ctx->cursor_init = IWKV_CURSOR_AFTER_LAST; + ctx->cursor_step = IWKV_CURSOR_PREV; + } + + if (!(aux->qmode & JQP_QRY_NOIDX) && ctx->jbc->idx) { // we have indexes associated with collection + rc = _jbi_collect_indexes(ctx, aux->expr, fctx, &snp); + RCRET(rc); + if (snp) { // Index selected + qsort(fctx, snp, sizeof(fctx[0]), _jbi_idx_cmp); + memcpy(&ctx->midx, &fctx[0], sizeof(ctx->midx)); + struct _JBMIDX *midx = &ctx->midx; + jqp_op_t op = midx->expr1->op->value; + if ((op == JQP_OP_EQ) || (op == JQP_OP_IN) || ((op == JQP_OP_GTE) && (ctx->cursor_init == IWKV_CURSOR_GE))) { + midx->expr1->prematched = true; + } + if (ctx->ux->log) { + iwxstr_cat2(ctx->ux->log, "[INDEX] SELECTED "); + _jbi_log_index_rules(ctx->ux->log, &ctx->midx); + } + if (midx->orderby_support && (aux->orderby_num == 1)) { + // Turn off final sorting since it supported by natural index scan order + ctx->sorting = false; + } else if (aux->orderby_num) { + ctx->sorting = true; + } + } else if (ctx->sorting) { // Last chance to use index and avoid sorting + if (_jbi_select_index_for_orderby(ctx) && ctx->ux->log) { + iwxstr_cat2(ctx->ux->log, "[INDEX] SELECTED "); + _jbi_log_index_rules(ctx->ux->log, &ctx->midx); + } + } + } + return rc; +} diff --git a/src/jbi/jbi_sorter_consumer.c b/src/jbi/jbi_sorter_consumer.c new file mode 100644 index 0000000..0066639 --- /dev/null +++ b/src/jbi/jbi_sorter_consumer.c @@ -0,0 +1,290 @@ +#include "ejdb2_internal.h" +#include "sort_r.h" + +static void _jbi_scan_sorter_release(struct _JBEXEC *ctx) { + struct _JBSSC *ssc = &ctx->ssc; + free(ssc->refs); + if (ssc->sof_active) { + ssc->sof.close(&ssc->sof); + } else { + free(ssc->docs); + } + memset(ssc, 0, sizeof(*ssc)); +} + +static int _jbi_scan_sorter_cmp(const void *o1, const void *o2, void *op) { + int rv = 0; + uint32_t r1, r2; + struct _JBL d1, d2; + struct _JBEXEC *ctx = op; + struct _JBSSC *ssc = &ctx->ssc; + struct JQP_AUX *aux = ctx->ux->q->aux; + uint8_t *p1, *p2; + assert(aux->orderby_num > 0); + + memcpy(&r1, o1, sizeof(r1)); + memcpy(&r2, o2, sizeof(r2)); + + p1 = ssc->docs + r1 + sizeof(uint64_t) /*id*/; + p2 = ssc->docs + r2 + sizeof(uint64_t) /*id*/; + + iwrc rc = jbl_from_buf_keep_onstack2(&d1, p1); + RCGO(rc, finish); + rc = jbl_from_buf_keep_onstack2(&d2, p2); + RCGO(rc, finish); + + for (int i = 0; i < aux->orderby_num; ++i) { + struct _JBL v1 = { 0 }; + struct _JBL v2 = { 0 }; + JBL_PTR ptr = aux->orderby_ptrs[i]; + int desc = (ptr->op & 1) ? -1 : 1; // If `-1` do desc sorting + _jbl_at(&d1, ptr, &v1); + _jbl_at(&d2, ptr, &v2); + rv = _jbl_cmp_atomic_values(&v1, &v2) * desc; + if (rv) { + break; + } + } + +finish: + if (rc) { + ssc->rc = rc; + longjmp(ssc->fatal_jmp, 1); + } + return rv; +} + +static iwrc _jbi_scan_sorter_apply(IWPOOL *pool, struct _JBEXEC *ctx, JQL q, struct _EJDB_DOC *doc) { + JBL_NODE root; + JBL jbl = doc->raw; + struct JQP_AUX *aux = q->aux; + iwrc rc = jbl_to_node(jbl, &root, true, pool); + RCRET(rc); + doc->node = root; + if (aux->qmode & JQP_QRY_APPLY_DEL) { + rc = jb_del(ctx->jbc, jbl, doc->id); + RCRET(rc); + } else if (aux->apply || aux->apply_placeholder) { + struct _JBL sn = { 0 }; + rc = jql_apply(q, root, pool); + RCRET(rc); + rc = _jbl_from_node(&sn, root); + RCRET(rc); + rc = jb_put(ctx->jbc, &sn, doc->id); + binn_free(&sn.bn); + RCRET(rc); + } + if (aux->projection) { + rc = jql_project(q, root, ctx->ux->pool, ctx); + } + return rc; +} + +static iwrc _jbi_scan_sorter_do(struct _JBEXEC *ctx) { + iwrc rc = 0; + int64_t step = 1, id; + struct _JBL jbl; + EJDB_EXEC *ux = ctx->ux; + struct _JBSSC *ssc = &ctx->ssc; + uint32_t rnum = ssc->refs_num; + struct JQP_AUX *aux = ux->q->aux; + IWPOOL *pool = ux->pool; + + if (rnum) { + if (setjmp(ssc->fatal_jmp)) { // Init error jump + rc = ssc->rc; + goto finish; + } + if (!ssc->docs) { + size_t sp; + rc = ssc->sof.probe_mmap(&ssc->sof, 0, &ssc->docs, &sp); + RCGO(rc, finish); + } + + sort_r(ssc->refs, rnum, sizeof(ssc->refs[0]), _jbi_scan_sorter_cmp, ctx); + } + + for (int64_t i = ux->skip; step && i < rnum && i >= 0; ) { + uint8_t *rp = ssc->docs + ssc->refs[i]; + memcpy(&id, rp, sizeof(id)); + rp += sizeof(id); + rc = jbl_from_buf_keep_onstack2(&jbl, rp); + RCGO(rc, finish); + struct _EJDB_DOC doc = { + .id = id, + .raw = &jbl + }; + if (aux->apply || aux->projection) { + if (!pool) { + pool = iwpool_create(jbl.bn.size * 2); + if (!pool) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + } + rc = _jbi_scan_sorter_apply(pool, ctx, ux->q, &doc); + RCGO(rc, finish); + } else if (aux->qmode & JQP_QRY_APPLY_DEL) { + rc = jb_del(ctx->jbc, &jbl, id); + RCGO(rc, finish); + } + if (!(aux->qmode & JQP_QRY_AGGREGATE)) { + do { + step = 1; + rc = ux->visitor(ux, &doc, &step); + RCGO(rc, finish); + } while (step == -1); + } + ++ux->cnt; + i += step; + if (pool != ux->pool) { + iwpool_destroy(pool); + pool = 0; + } + if (--ux->limit < 1) { + break; + } + } + +finish: + if (pool != ux->pool) { + iwpool_destroy(pool); + } + _jbi_scan_sorter_release(ctx); + return rc; +} + +static iwrc _jbi_scan_sorter_init(struct _JBSSC *ssc, off_t initial_size) { + IWFS_EXT_OPTS opts = { + .initial_size = initial_size, + .rspolicy = iw_exfile_szpolicy_fibo, + .file = { + .path = "jb-", + .omode = IWFS_OTMP | IWFS_OUNLINK + } + }; + iwrc rc = iwfs_exfile_open(&ssc->sof, &opts); + RCRET(rc); + rc = ssc->sof.add_mmap(&ssc->sof, 0, SIZE_T_MAX, 0); + if (rc) { + ssc->sof.close(&ssc->sof); + } + return rc; +} + +iwrc jbi_sorter_consumer( + struct _JBEXEC *ctx, IWKV_cursor cur, int64_t id, + int64_t *step, bool *matched, iwrc err) { + if (!id) { + // End of scan + if (err) { + // In the case of error do not perform sorting just release resources + _jbi_scan_sorter_release(ctx); + return err; + } else { + return _jbi_scan_sorter_do(ctx); + } + } + + iwrc rc; + size_t vsz = 0; + struct _JBL jbl; + struct _JBSSC *ssc = &ctx->ssc; + EJDB db = ctx->jbc->db; + IWFS_EXT *sof = &ssc->sof; + +start: + { + if (cur) { + rc = iwkv_cursor_copy_val(cur, ctx->jblbuf + sizeof(id), ctx->jblbufsz - sizeof(id), &vsz); + } else { + IWKV_val key = { + .data = &id, + .size = sizeof(id) + }; + rc = iwkv_get_copy(ctx->jbc->cdb, &key, ctx->jblbuf + sizeof(id), ctx->jblbufsz - sizeof(id), &vsz); + } + if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + } else { + RCRET(rc); + } + if (vsz + sizeof(id) > ctx->jblbufsz) { + size_t nsize = MAX(vsz + sizeof(id), ctx->jblbufsz * 2); + void *nbuf = realloc(ctx->jblbuf, nsize); + if (!nbuf) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + ctx->jblbuf = nbuf; + ctx->jblbufsz = nsize; + goto start; + } + } + + rc = jbl_from_buf_keep_onstack(&jbl, ctx->jblbuf + sizeof(id), vsz); + RCRET(rc); + + rc = jql_matched(ctx->ux->q, &jbl, matched); + if (!*matched) { + return 0; + } + + if (!ssc->refs) { + ssc->refs_asz = 64 * 1024; // 64K + ssc->refs = malloc(db->opts.document_buffer_sz); + if (!ssc->refs) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + ssc->docs_asz = 128 * 1024; // 128K + ssc->docs = malloc(ssc->docs_asz); + if (!ssc->docs) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + } else if (ssc->refs_asz <= (ssc->refs_num + 1) * sizeof(ssc->refs[0])) { + ssc->refs_asz *= 2; + uint32_t *nrefs = realloc(ssc->refs, ssc->refs_asz); + if (!nrefs) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + ssc->refs = nrefs; + } + + vsz += sizeof(id); + memcpy(ctx->jblbuf, &id, sizeof(id)); + +start2: + { + if (ssc->docs) { + uint32_t rsize = ssc->docs_npos + vsz; + if (rsize > ssc->docs_asz) { + ssc->docs_asz = MIN(rsize * 2, db->opts.sort_buffer_sz); + if (rsize > ssc->docs_asz) { + size_t sz; + rc = _jbi_scan_sorter_init(ssc, (ssc->docs_npos + vsz) * 2); + RCRET(rc); + rc = sof->write(sof, 0, ssc->docs, ssc->docs_npos, &sz); + RCRET(rc); + free(ssc->docs); + ssc->docs = 0; + ssc->sof_active = true; + goto start2; + } else { + void *nbuf = realloc(ssc->docs, ssc->docs_asz); + if (!nbuf) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + ssc->docs = nbuf; + } + } + memcpy(ssc->docs + ssc->docs_npos, ctx->jblbuf, vsz); + } else { + size_t sz; + rc = sof->write(sof, ssc->docs_npos, ctx->jblbuf, vsz, &sz); + RCRET(rc); + } + ssc->refs[ssc->refs_num++] = ssc->docs_npos; + ssc->docs_npos += vsz; + } + + return rc; +} diff --git a/src/jbi/jbi_uniq_scanner.c b/src/jbi/jbi_uniq_scanner.c new file mode 100644 index 0000000..5cc96b8 --- /dev/null +++ b/src/jbi/jbi_uniq_scanner.c @@ -0,0 +1,241 @@ +#include "ejdb2_internal.h" + +static_assert(IW_VNUMBUFSZ <= JBNUMBUF_SIZE, "IW_VNUMBUFSZ <= JBNUMBUF_SIZE"); + +static iwrc _jbi_consume_eq(struct _JBEXEC *ctx, JQVAL *jqval, JB_SCAN_CONSUMER consumer) { + size_t sz; + uint64_t id; + int64_t step; + bool matched; + struct _JBMIDX *midx = &ctx->midx; + char numbuf[JBNUMBUF_SIZE]; + IWKV_val key; + + jbi_jqval_fill_ikey(midx->idx, jqval, &key, numbuf); + if (!key.size) { + return consumer(ctx, 0, 0, 0, 0, 0); + } + iwrc rc = iwkv_get_copy(midx->idx->idb, &key, numbuf, sizeof(numbuf), &sz); + if (rc) { + if (rc == IWKV_ERROR_NOTFOUND) { + return consumer(ctx, 0, 0, 0, 0, 0); + } else { + return rc; + } + } + IW_READVNUMBUF64_2(numbuf, id); + rc = consumer(ctx, 0, id, &step, &matched, 0); + return consumer(ctx, 0, 0, 0, 0, rc); +} + +static iwrc _jbi_consume_in_node(struct _JBEXEC *ctx, JQVAL *jqval, JB_SCAN_CONSUMER consumer) { + JQVAL jqv; + size_t sz; + uint64_t id; + bool matched; + char numbuf[JBNUMBUF_SIZE]; + + iwrc rc = 0; + int64_t step = 1; + IWKV_val key; + struct _JBMIDX *midx = &ctx->midx; + JBL_NODE nv = jqval->vnode->child; + + if (!nv) { + return consumer(ctx, 0, 0, 0, 0, 0); + } + do { + jql_node_to_jqval(nv, &jqv); + jbi_jqval_fill_ikey(midx->idx, &jqv, &key, numbuf); + if (!key.size) { + continue; + } + rc = iwkv_get_copy(midx->idx->idb, &key, numbuf, sizeof(numbuf), &sz); + if (rc) { + if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + continue; + } else { + goto finish; + } + } + if (step > 0) { + --step; + } else if (step < 0) { + ++step; + } + if (!step) { + IW_READVNUMBUF64_2(numbuf, id); + step = 1; + rc = consumer(ctx, 0, id, &step, &matched, 0); + RCGO(rc, finish); + } + } while (step && (step > 0 ? (nv = nv->next) : (nv = nv->prev))); + +finish: + return consumer(ctx, 0, 0, 0, 0, rc); +} + +static iwrc _jbi_consume_scan(struct _JBEXEC *ctx, JQVAL *jqval, JB_SCAN_CONSUMER consumer) { + size_t sz; + IWKV_cursor cur; + char numbuf[JBNUMBUF_SIZE]; + + int64_t step = 1; + struct _JBMIDX *midx = &ctx->midx; + JBIDX idx = midx->idx; + jqp_op_t expr1_op = midx->expr1->op->value; + + IWKV_val key; + jbi_jqval_fill_ikey(idx, jqval, &key, numbuf); + + iwrc rc = iwkv_cursor_open(idx->idb, &cur, midx->cursor_init, &key); + if ((rc == IWKV_ERROR_NOTFOUND) && ((expr1_op == JQP_OP_LT) || (expr1_op == JQP_OP_LTE))) { + iwkv_cursor_close(&cur); + midx->cursor_init = IWKV_CURSOR_BEFORE_FIRST; + midx->cursor_step = IWKV_CURSOR_NEXT; + rc = iwkv_cursor_open(idx->idb, &cur, midx->cursor_init, 0); + RCGO(rc, finish); + if (!midx->expr2) { // Fail fast + midx->expr2 = midx->expr1; + } + } else if (rc) { + goto finish; + } + + IWKV_cursor_op cursor_reverse_step = (midx->cursor_step == IWKV_CURSOR_NEXT) + ? IWKV_CURSOR_PREV : IWKV_CURSOR_NEXT; + + if (midx->cursor_init < IWKV_CURSOR_NEXT) { // IWKV_CURSOR_BEFORE_FIRST || IWKV_CURSOR_AFTER_LAST + rc = iwkv_cursor_to(cur, midx->cursor_step); + RCGO(rc, finish); + } + do { + if (step > 0) { + --step; + } else if (step < 0) { + ++step; + } + if (!step) { + int64_t id; + bool matched = false; + rc = iwkv_cursor_copy_val(cur, &numbuf, IW_VNUMBUFSZ, &sz); + RCGO(rc, finish); + if (sz > IW_VNUMBUFSZ) { + rc = IWKV_ERROR_CORRUPTED; + iwlog_ecode_error3(rc); + break; + } + IW_READVNUMBUF64_2(numbuf, id); + if ( midx->expr2 + && !midx->expr2->prematched + && !jbi_node_expr_matched(ctx->ux->q->aux, midx->idx, cur, midx->expr2, &rc)) { + break; + } + if ( (expr1_op == JQP_OP_PREFIX) + && !jbi_node_expr_matched(ctx->ux->q->aux, midx->idx, cur, midx->expr1, &rc)) { + break; + } + RCGO(rc, finish); + + step = 1; + rc = consumer(ctx, 0, id, &step, &matched, 0); + RCGO(rc, finish); + if (!midx->expr1->prematched && matched && (expr1_op != JQP_OP_PREFIX)) { + // Further scan will always match the main index expression + midx->expr1->prematched = true; + } + } + } while (step && !(rc = iwkv_cursor_to(cur, step > 0 ? midx->cursor_step : cursor_reverse_step))); + +finish: + if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + } + if (cur) { + iwkv_cursor_close(&cur); + } + return consumer(ctx, 0, 0, 0, 0, rc); +} + +iwrc _jbi_consume_noxpr_scan(struct _JBEXEC *ctx, JB_SCAN_CONSUMER consumer) { + size_t sz; + IWKV_cursor cur; + char numbuf[JBNUMBUF_SIZE]; + int64_t step = 1; + struct _JBMIDX *midx = &ctx->midx; + IWKV_cursor_op cursor_reverse_step = (midx->cursor_step == IWKV_CURSOR_NEXT) + ? IWKV_CURSOR_PREV : IWKV_CURSOR_NEXT; + + iwrc rc = iwkv_cursor_open(midx->idx->idb, &cur, midx->cursor_init, 0); + RCGO(rc, finish); + if (midx->cursor_init < IWKV_CURSOR_NEXT) { // IWKV_CURSOR_BEFORE_FIRST || IWKV_CURSOR_AFTER_LAST + rc = iwkv_cursor_to(cur, midx->cursor_step); + RCGO(rc, finish); + } + do { + if (step > 0) { + --step; + } else if (step < 0) { + ++step; + } + if (!step) { + int64_t id; + bool matched; + rc = iwkv_cursor_copy_val(cur, &numbuf, IW_VNUMBUFSZ, &sz); + RCGO(rc, finish); + if (sz > IW_VNUMBUFSZ) { + rc = IWKV_ERROR_CORRUPTED; + iwlog_ecode_error3(rc); + break; + } + IW_READVNUMBUF64_2(numbuf, id); + RCGO(rc, finish); + step = 1; + rc = consumer(ctx, 0, id, &step, &matched, 0); + RCGO(rc, finish); + } + } while (step && !(rc = iwkv_cursor_to(cur, step > 0 ? midx->cursor_step : cursor_reverse_step))); + +finish: + if (rc == IWKV_ERROR_NOTFOUND) { + rc = 0; + } + if (cur) { + iwkv_cursor_close(&cur); + } + return consumer(ctx, 0, 0, 0, 0, rc); +} + +iwrc jbi_uniq_scanner(struct _JBEXEC *ctx, JB_SCAN_CONSUMER consumer) { + iwrc rc; + struct _JBMIDX *midx = &ctx->midx; + if (!midx->expr1) { + return _jbi_consume_noxpr_scan(ctx, consumer); + } + JQP_QUERY *qp = ctx->ux->q->qp; + JQVAL *jqval = jql_unit_to_jqval(qp->aux, midx->expr1->right, &rc); + RCRET(rc); + switch (midx->expr1->op->value) { + case JQP_OP_EQ: + return _jbi_consume_eq(ctx, jqval, consumer); + case JQP_OP_IN: + if (jqval->type == JQVAL_JBLNODE) { + return _jbi_consume_in_node(ctx, jqval, consumer); + } else { + iwlog_ecode_error3(IW_ERROR_ASSERTION); + return IW_ERROR_ASSERTION; + } + break; + default: + break; + } + if ((midx->expr1->op->value == JQP_OP_GT) && (jqval->type == JQVAL_I64)) { + JQVAL mjqv; + memcpy(&mjqv, jqval, sizeof(*jqval)); + mjqv.vi64 = mjqv.vi64 + 1; // Because for index scan we use `IWKV_CURSOR_GE` + return _jbi_consume_scan(ctx, &mjqv, consumer); + } else { + return _jbi_consume_scan(ctx, jqval, consumer); + } +} diff --git a/src/jbi/jbi_util.c b/src/jbi/jbi_util.c new file mode 100644 index 0000000..efa3ade --- /dev/null +++ b/src/jbi/jbi_util.c @@ -0,0 +1,283 @@ +#include "ejdb2_internal.h" +#include "convert.h" +#include + +// --------------------------------------------------------------------------- + +// fixme: code duplication below +void jbi_jbl_fill_ikey(JBIDX idx, JBL jbv, IWKV_val *ikey, char numbuf[static JBNUMBUF_SIZE]) { + int64_t *llv = (void*) numbuf; + jbl_type_t jbvt = jbl_type(jbv); + ejdb_idx_mode_t itype = (idx->mode & ~(EJDB_IDX_UNIQUE)); + ikey->size = 0; + ikey->data = 0; + + switch (itype) { + case EJDB_IDX_STR: + switch (jbvt) { + case JBV_STR: + ikey->size = jbl_size(jbv); + ikey->data = (void*) jbl_get_str(jbv); + break; + case JBV_I64: + ikey->size = (size_t) iwitoa(jbl_get_i64(jbv), numbuf, JBNUMBUF_SIZE); + ikey->data = numbuf; + break; + case JBV_BOOL: + if (jbl_get_i32(jbv)) { + ikey->size = sizeof("true"); + ikey->data = "true"; + } else { + ikey->size = sizeof("false"); + ikey->data = "false"; + } + break; + case JBV_F64: + jbi_ftoa(jbl_get_f64(jbv), numbuf, &ikey->size); + ikey->data = numbuf; + break; + default: + break; + } + break; + case EJDB_IDX_I64: + ikey->size = sizeof(*llv); + ikey->data = llv; + switch (jbvt) { + case JBV_I64: + case JBV_F64: + case JBV_BOOL: + *llv = jbl_get_i64(jbv); + break; + case JBV_STR: + *llv = iwatoi(jbl_get_str(jbv)); + break; + default: + ikey->size = 0; + ikey->data = 0; + break; + } + break; + case EJDB_IDX_F64: + ikey->data = numbuf; + switch (jbvt) { + case JBV_F64: + case JBV_I64: + case JBV_BOOL: + jbi_ftoa(jbl_get_f64(jbv), numbuf, &ikey->size); + break; + case JBV_STR: + jbi_ftoa(iwatof(jbl_get_str(jbv)), numbuf, &ikey->size); + break; + default: + ikey->size = 0; // -V1048 + ikey->data = 0; + break; + } + break; + default: + break; + } +} + +void jbi_jqval_fill_ikey(JBIDX idx, const JQVAL *jqval, IWKV_val *ikey, char numbuf[static JBNUMBUF_SIZE]) { + int64_t *llv = (void*) numbuf; + ikey->size = 0; + ikey->data = numbuf; + ejdb_idx_mode_t itype = (idx->mode & ~(EJDB_IDX_UNIQUE)); + jqval_type_t jqvt = jqval->type; + + switch (itype) { + case EJDB_IDX_STR: + switch (jqvt) { + case JQVAL_STR: + ikey->size = strlen(jqval->vstr); + ikey->data = (void*) jqval->vstr; + break; + case JQVAL_I64: + ikey->size = (size_t) iwitoa(jqval->vi64, numbuf, JBNUMBUF_SIZE); + break; + case JQVAL_BOOL: + if (jqval->vbool) { + ikey->size = sizeof("true"); + ikey->data = "true"; + } else { + ikey->size = sizeof("false"); + ikey->data = "false"; + } + break; + case JQVAL_F64: + jbi_ftoa(jqval->vf64, numbuf, &ikey->size); + break; + default: + break; + } + break; + case EJDB_IDX_I64: + ikey->size = sizeof(*llv); + switch (jqvt) { + case JQVAL_I64: + *llv = jqval->vi64; + break; + case JQVAL_F64: + *llv = (int64_t) jqval->vf64; + break; + case JQVAL_BOOL: + *llv = jqval->vbool; + break; + case JQVAL_STR: + *llv = iwatoi(jqval->vstr); + break; + default: + ikey->data = 0; + break; + } + break; + case EJDB_IDX_F64: + switch (jqvt) { + case JQVAL_F64: + jbi_ftoa(jqval->vf64, numbuf, &ikey->size); + break; + case JQVAL_I64: + jbi_ftoa(jqval->vi64, numbuf, &ikey->size); + break; + case JQVAL_BOOL: + jbi_ftoa(jqval->vbool, numbuf, &ikey->size); + break; + case JQVAL_STR: + jbi_ftoa(iwatof(jqval->vstr), numbuf, &ikey->size); + break; + default: + ikey->data = 0; + break; + } + break; + default: + break; + } +} + +void jbi_node_fill_ikey(JBIDX idx, JBL_NODE node, IWKV_val *ikey, char numbuf[static JBNUMBUF_SIZE]) { + int64_t *llv = (void*) numbuf; + ikey->size = 0; + ikey->data = numbuf; + ejdb_idx_mode_t itype = (idx->mode & ~(EJDB_IDX_UNIQUE)); + jbl_type_t jbvt = node->type; + + switch (itype) { + case EJDB_IDX_STR: + switch (jbvt) { + case JBV_STR: + ikey->size = (size_t) node->vsize; + ikey->data = (void*) node->vptr; + break; + case JBV_I64: + ikey->size = (size_t) iwitoa(node->vi64, numbuf, JBNUMBUF_SIZE); + break; + case JBV_BOOL: + if (node->vbool) { + ikey->size = sizeof("true"); + ikey->data = "true"; + } else { + ikey->size = sizeof("false"); + ikey->data = "false"; + } + break; + case JBV_F64: + jbi_ftoa(node->vf64, numbuf, &ikey->size); + break; + default: + break; + } + break; + case EJDB_IDX_I64: + ikey->size = sizeof(*llv); + switch (jbvt) { + case JBV_I64: + *llv = node->vi64; + break; + case JBV_F64: + *llv = (int64_t) node->vf64; + break; + case JBV_BOOL: + *llv = node->vbool; + break; + case JBV_STR: + *llv = iwatoi(node->vptr); + break; + default: + ikey->size = 0; + ikey->data = 0; + break; + } + break; + case EJDB_IDX_F64: + switch (jbvt) { + case JBV_F64: + jbi_ftoa(node->vf64, numbuf, &ikey->size); + break; + case JBV_I64: + jbi_ftoa(node->vi64, numbuf, &ikey->size); + break; + case JBV_BOOL: + jbi_ftoa(node->vbool, numbuf, &ikey->size); + break; + case JBV_STR: + jbi_ftoa(iwatof(node->vptr), numbuf, &ikey->size); + break; + default: + ikey->data = 0; + break; + } + break; + default: + break; + } +} + +bool jbi_node_expr_matched(JQP_AUX *aux, JBIDX idx, IWKV_cursor cur, JQP_EXPR *expr, iwrc *rcp) { + size_t sz; + char skey[1024]; + char *kbuf = skey; + bool ret = false; + iwrc rc = 0; + + if (!(idx->mode & (EJDB_IDX_STR | EJDB_IDX_I64 | EJDB_IDX_F64))) { + return false; + } + JQVAL lv, *rv = jql_unit_to_jqval(aux, expr->right, &rc); + RCGO(rc, finish); + + rc = iwkv_cursor_copy_key(cur, kbuf, sizeof(skey) - 1, &sz, 0); + RCGO(rc, finish); + if (sz > sizeof(skey) - 1) { + kbuf = malloc(sz); + if (!kbuf) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + rc = iwkv_cursor_copy_key(cur, kbuf, sizeof(skey) - 1, &sz, 0); + RCGO(rc, finish); + } + if (idx->mode & EJDB_IDX_STR) { + kbuf[sz] = '\0'; + lv.type = JQVAL_STR; + lv.vstr = kbuf; + } else if (idx->mode & EJDB_IDX_I64) { + memcpy(&lv.vi64, kbuf, sizeof(lv.vi64)); + lv.type = JQVAL_I64; + } else if (idx->mode & EJDB_IDX_F64) { + kbuf[sz] = '\0'; + lv.type = JQVAL_F64; + lv.vf64 = (double) iwatof(kbuf); + } + + ret = jql_match_jqval_pair(aux, &lv, expr->op, rv, &rc); + +finish: + if (kbuf != skey) { + free(kbuf); + } + *rcp = rc; + return ret; +} diff --git a/src/jbl/binn.c b/src/jbl/binn.c new file mode 100644 index 0000000..dc7c59d --- /dev/null +++ b/src/jbl/binn.c @@ -0,0 +1,3154 @@ +#include +#include +#include +#include +#include +#include +#include "binn.h" +#include + +#define INT64_FORMAT PRId64 +#define UINT64_FORMAT PRIu64 +#define INT64_HEX_FORMAT PRIx64 + +#define UNUSED(x) (void) (x) +#define round(dbl) dbl >= 0.0 ? (int) (dbl + 0.5) : ((dbl - (double) (int) dbl) <= -0.5 ? (int) dbl : (int) (dbl - 0.5)) + +#define CHUNK_SIZE 256 // 1024 + +#define BINN_STRUCT 1 +#define BINN_BUFFER 2 + +void*(*malloc_fn)(size_t len) = malloc; +void* (*realloc_fn)(void *ptr, size_t len) = realloc; +void (*free_fn)(void *ptr) = free; + +#if defined(__APPLE__) || defined(_WIN32) +#define __BIG_ENDIAN 0x1000 +#define __LITTLE_ENDIAN 0x0001 +#define __BYTE_ORDER __LITTLE_ENDIAN +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) +#include +#define __BIG_ENDIAN BIG_ENDIAN +#define __LITTLE_ENDIAN LITTLE_ENDIAN +#define __BYTE_ORDER BYTE_ORDER +#elif defined(_AIX) +#include +#define __BIG_ENDIAN BIG_ENDIAN +#define __LITTLE_ENDIAN LITTLE_ENDIAN +#define __BYTE_ORDER BYTE_ORDER +#else +#include +#endif + +#ifndef __BYTE_ORDER +#error "__BYTE_ORDER not defined" +#endif +#ifndef __BIG_ENDIAN +#error "__BIG_ENDIAN not defined" +#endif +#ifndef __LITTLE_ENDIAN +#error "__LITTLE_ENDIAN not defined" +#endif +#if __BIG_ENDIAN == __LITTLE_ENDIAN +#error "__BIG_ENDIAN == __LITTLE_ENDIAN" +#endif + +#if __BYTE_ORDER == __BIG_ENDIAN +#define tobe16(x) (x) +#define tobe32(x) (x) +#define tobe64(x) (x) +#else +#define tobe16(x) IW_SWAB16(x) +#define tobe32(x) IW_SWAB32(x) +#define tobe64(x) IW_SWAB64(x) +#endif + +#define frombe16 tobe16 +#define frombe32 tobe32 +#define frombe64 tobe64 + +#ifndef WIN32 +#define stricmp strcasecmp +#define strnicmp strncasecmp +#endif + +void binn_set_alloc_functions( + void*(*new_malloc)(size_t), void*(*new_realloc)(void*, size_t), + void (*new_free)(void*)) { + malloc_fn = new_malloc; + realloc_fn = new_realloc; + free_fn = new_free; +} + +ALWAYS_INLINE void *binn_malloc(int size) { + return malloc_fn(size); +} + +BINN_PRIVATE void *binn_memdup(const void *src, int size) { + void *dest; + if ((src == NULL) || (size <= 0)) { + return NULL; + } + dest = binn_malloc(size); + if (dest == NULL) { + return NULL; + } + memcpy(dest, src, size); + return dest; +} + +BINN_PRIVATE size_t strlen2(char *str) { + if (str == NULL) { + return 0; + } + return strlen(str); +} + +int binn_create_type(int storage_type, int data_type_index) { + if (data_type_index < 0) { + return -1; + } + if ((storage_type < BINN_STORAGE_MIN) || (storage_type > BINN_STORAGE_MAX)) { + return -1; + } + if (data_type_index < 16) { + return storage_type | data_type_index; + } else if (data_type_index < 4096) { + storage_type |= BINN_STORAGE_HAS_MORE; + storage_type <<= 8; + data_type_index >>= 4; + return storage_type | data_type_index; + } else { + return -1; + } +} + +BOOL binn_get_type_info(int long_type, int *pstorage_type, int *pextra_type) { + int storage_type, extra_type; + BOOL retval = TRUE; + +again: + if (long_type < 0) { + goto loc_invalid; + } else if (long_type <= 0xff) { + storage_type = long_type & BINN_STORAGE_MASK; + extra_type = long_type & BINN_TYPE_MASK; + } else if (long_type <= 0xffff) { + storage_type = long_type & BINN_STORAGE_MASK16; + storage_type >>= 8; + extra_type = long_type & BINN_TYPE_MASK16; + extra_type >>= 4; + } else if (long_type & BINN_STORAGE_VIRTUAL) { + //storage_type = BINN_STORAGE_VIRTUAL; + //extra_type = xxx; + long_type &= 0xffff; + goto again; + } else { +loc_invalid: + storage_type = -1; + extra_type = -1; + retval = FALSE; + } + if (pstorage_type) { + *pstorage_type = storage_type; + } + if (pextra_type) { + *pextra_type = extra_type; + } + return retval; +} + +BOOL binn_create(binn *item, int type, int size, void *pointer) { + BOOL retval = FALSE; + + switch (type) { + case BINN_LIST: + case BINN_MAP: + case BINN_OBJECT: + break; + default: + goto loc_exit; + } + if ((item == NULL) || (size < 0)) { + goto loc_exit; + } + if (size < MIN_BINN_SIZE) { + if (pointer) { + goto loc_exit; + } else { + size = 0; + } + } + memset(item, 0, sizeof(binn)); + + if (pointer) { + item->pre_allocated = TRUE; + item->pbuf = pointer; + item->alloc_size = size; + } else { + item->pre_allocated = FALSE; + if (size == 0) { + size = CHUNK_SIZE; + } + pointer = binn_malloc(size); + if (pointer == 0) { + return INVALID_BINN; + } + item->pbuf = pointer; + item->alloc_size = size; + } + + item->header = BINN_MAGIC; + item->writable = TRUE; + item->used_size = MAX_BINN_HEADER; // save space for the header + item->type = type; + item->dirty = TRUE; // the header is not written to the buffer + retval = TRUE; + +loc_exit: + return retval; +} + +binn *binn_new(int type, int size, void *pointer) { + binn *item; + item = (binn*) binn_malloc(sizeof(binn)); + if (binn_create(item, type, size, pointer) == FALSE) { + free_fn(item); + return NULL; + } + item->allocated = TRUE; + return item; +} + +BOOL binn_create_list(binn *list) { + return binn_create(list, BINN_LIST, 0, NULL); +} + +BOOL binn_create_map(binn *map) { + return binn_create(map, BINN_MAP, 0, NULL); +} + +BOOL binn_create_object(binn *object) { + return binn_create(object, BINN_OBJECT, 0, NULL); +} + +binn *binn_list() { + return binn_new(BINN_LIST, 0, 0); +} + +binn *binn_map() { + return binn_new(BINN_MAP, 0, 0); +} + +binn *binn_object() { + return binn_new(BINN_OBJECT, 0, 0); +} + +BOOL binn_load(void *data, binn *value) { + if ((data == NULL) || (value == NULL)) { + return FALSE; + } + memset(value, 0, sizeof(binn)); + value->header = BINN_MAGIC; + if (binn_is_valid(data, &value->type, &value->count, &value->size) == FALSE) { + return FALSE; + } + value->ptr = data; + return TRUE; +} + +binn *binn_open(void *data) { + binn *item; + item = (binn*) binn_malloc(sizeof(binn)); + if (binn_load(data, item) == FALSE) { + free_fn(item); + return NULL; + } + item->allocated = TRUE; + return item; +} + +BINN_PRIVATE int binn_get_ptr_type(const void *ptr) { + if (ptr == NULL) { + return 0; + } + switch (*(const unsigned int*) ptr) { + case BINN_MAGIC: + return BINN_STRUCT; + default: + return BINN_BUFFER; + } +} + +BOOL binn_is_struct(void *ptr) { + if (ptr == NULL) { + return FALSE; + } + if ((*(unsigned int*) ptr) == BINN_MAGIC) { + return TRUE; + } else { + return FALSE; + } +} + +BINN_PRIVATE int CalcAllocation(int needed_size, int alloc_size) { + int calc_size; + calc_size = alloc_size; + while (calc_size < needed_size) { + calc_size <<= 1; // same as *= 2 + //calc_size += CHUNK_SIZE; -- this is slower than the above line, because there are more reallocations + } + return calc_size; +} + +BINN_PRIVATE BOOL CheckAllocation(binn *item, int add_size) { + int alloc_size; + void *ptr; + if (item->used_size + add_size > item->alloc_size) { + if (item->pre_allocated) { + return FALSE; + } + alloc_size = CalcAllocation(item->used_size + add_size, item->alloc_size); + ptr = realloc_fn(item->pbuf, alloc_size); + if (ptr == NULL) { + return FALSE; + } + item->pbuf = ptr; + item->alloc_size = alloc_size; + } + return TRUE; +} + +#if __BYTE_ORDER == __BIG_ENDIAN + +BINN_PRIVATE int get_storage_size(int storage_type) { + switch (storage_type) { + case BINN_STORAGE_NOBYTES: + return 0; + case BINN_STORAGE_BYTE: + return 1; + case BINN_STORAGE_WORD: + return 2; + case BINN_STORAGE_DWORD: + return 4; + case BINN_STORAGE_QWORD: + return 8; + default: + return 0; + } +} + +#endif + +BINN_PRIVATE unsigned char *AdvanceDataPos(unsigned char *p, unsigned char *plimit) { + unsigned char byte; + int storage_type, dsize; + if (p > plimit) { + return 0; + } + + byte = *p; + p++; + storage_type = byte & BINN_STORAGE_MASK; + if (byte & BINN_STORAGE_HAS_MORE) { + p++; + } + + switch (storage_type) { + case BINN_STORAGE_NOBYTES: + break; + case BINN_STORAGE_BYTE: + p++; + break; + case BINN_STORAGE_WORD: + p += 2; + break; + case BINN_STORAGE_DWORD: + p += 4; + break; + case BINN_STORAGE_QWORD: + p += 8; + break; + case BINN_STORAGE_BLOB: + if (p + sizeof(int) - 1 > plimit) { + return 0; + } + memcpy(&dsize, p, 4); + dsize = frombe32(dsize); + p += 4 + dsize; + break; + case BINN_STORAGE_CONTAINER: + if (p > plimit) { + return 0; + } + dsize = *((unsigned char*) p); + if (dsize & 0x80) { + if (p + sizeof(int) - 1 > plimit) { + return 0; + } + memcpy(&dsize, p, 4); + dsize = frombe32(dsize); + dsize &= 0x7FFFFFFF; + } + dsize--; // remove the type byte already added before + p += dsize; + break; + case BINN_STORAGE_STRING: + if (p > plimit) { + return 0; + } + dsize = *((unsigned char*) p); + if (dsize & 0x80) { + if (p + sizeof(int) - 1 > plimit) { + return 0; + } + memcpy(&dsize, p, 4); + p += 4; + dsize = frombe32(dsize); + dsize &= 0x7FFFFFFF; + } else { + p++; + } + p += dsize; + p++; // null terminator. + break; + default: + return 0; + } + if (p > plimit) { + return 0; + } + return p; +} + +BINN_PRIVATE unsigned char *SearchForID(unsigned char *p, int header_size, int size, int numitems, int id) { + unsigned char *plimit, *base; + int i, int32; + + base = p; + plimit = p + size - 1; + p += header_size; + + // search for the ID in all the arguments. + for (i = 0; i < numitems; i++) { + memcpy(&int32, p, 4); + p += 4; + int32 = frombe32(int32); + if (p > plimit) { + break; + } + // Compare if the IDs are equal. + if (int32 == id) { + return p; + } + // xxx + p = AdvanceDataPos(p, plimit); + if ((p == 0) || (p < base)) { + break; + } + } + return NULL; +} + +BINN_PRIVATE unsigned char *SearchForKey( + unsigned char *p, int header_size, int size, int numitems, const char *key, + int keylen) { + unsigned char len, *plimit, *base; + int i; + + base = p; + plimit = p + size - 1; + p += header_size; + + // search for the key in all the arguments. + for (i = 0; i < numitems; i++) { + len = *((unsigned char*) p); + p++; + if (p > plimit) { + break; + } + // Compare if the strings are equal. + if (len > 0) { + if (strnicmp((char*) p, key, len) == 0) { // note that there is no null terminator here + if (keylen == len) { + p += len; + return p; + } + } + p += len; + if (p > plimit) { + break; + } + } else if (len == keylen) { // in the case of empty string: "" + return p; + } + // xxx + p = AdvanceDataPos(p, plimit); + if ((p == 0) || (p < base)) { + break; + } + } + return NULL; +} + +BINN_PRIVATE BOOL AddValue(binn *item, int type, void *pvalue, int size); + +BINN_PRIVATE BOOL binn_list_add_raw(binn *item, int type, void *pvalue, int size) { + if ((item == NULL) || (item->type != BINN_LIST) || (item->writable == FALSE)) { + return FALSE; + } + //if (CheckAllocation(item, 4) == FALSE) return FALSE; // 4 bytes used for data_store and data_format. + if (AddValue(item, type, pvalue, size) == FALSE) { + return FALSE; + } + item->count++; + return TRUE; +} + +BINN_PRIVATE BOOL binn_object_set_raw(binn *item, const char *key, int keylen, int type, void *pvalue, int size) { + unsigned char *p, len; + int int32 = keylen; + + if ( (key == NULL) + || (item == NULL) + || (item->type != BINN_OBJECT) + || (item->writable == FALSE) + || (keylen > 255)) { + return FALSE; + } + + // is the key already in it? + p = SearchForKey(item->pbuf, MAX_BINN_HEADER, item->used_size, item->count, key, keylen); + if (p) { + return FALSE; + } + + // start adding it + if (CheckAllocation(item, 1 + int32) == FALSE) { + return FALSE; // bytes used for the key size and the key itself. + } + p = ((unsigned char*) item->pbuf) + item->used_size; + len = int32; + *p = len; + p++; + memcpy(p, key, int32); + int32++; // now contains the strlen + 1 byte for the len + item->used_size += int32; + + if (AddValue(item, type, pvalue, size) == FALSE) { + item->used_size -= int32; + return FALSE; + } + item->count++; + return TRUE; +} + +BINN_PRIVATE BOOL binn_map_set_raw(binn *item, int id, int type, void *pvalue, int size) { + unsigned char *p; + int int32; + + if ((item == NULL) || (item->type != BINN_MAP) || (item->writable == FALSE)) { + return FALSE; + } + // is the ID already in it? + p = SearchForID(item->pbuf, MAX_BINN_HEADER, item->used_size, item->count, id); + if (p) { + return FALSE; + } + if (CheckAllocation(item, 4) == FALSE) { + return FALSE; // 4 bytes used for the id. + } + int32 = tobe32(id); + p = ((unsigned char*) item->pbuf) + item->used_size; + + memcpy(p, &int32, 4); + item->used_size += 4; + + if (AddValue(item, type, pvalue, size) == FALSE) { + item->used_size -= 4; + return FALSE; + } + item->count++; + return TRUE; +} + +BINN_PRIVATE void *compress_int(int *pstorage_type, int *ptype, void *psource) { + int storage_type, storage_type2, type, type2 = 0; + int64 vint = 0; + uint64 vuint; + char *pvalue; +#if __BYTE_ORDER == __BIG_ENDIAN + int size1, size2; +#endif + + storage_type = *pstorage_type; + if (storage_type == BINN_STORAGE_BYTE) { + return psource; + } + + type = *ptype; + + switch (type) { + case BINN_INT64: + vint = *(int64*) psource; + goto loc_signed; + case BINN_INT32: + vint = *(int*) psource; + goto loc_signed; + case BINN_INT16: + vint = *(short*) psource; + goto loc_signed; + case BINN_UINT64: + vuint = *(uint64*) psource; + goto loc_positive; + case BINN_UINT32: + vuint = *(unsigned int*) psource; + goto loc_positive; + case BINN_UINT16: + vuint = *(unsigned short*) psource; + goto loc_positive; + } + +loc_signed: + if (vint >= 0) { + vuint = vint; + goto loc_positive; + } + //loc_negative: + if (vint >= INT8_MIN) { + type2 = BINN_INT8; + } else if (vint >= INT16_MIN) { + type2 = BINN_INT16; + } else if (vint >= INT32_MIN) { + type2 = BINN_INT32; + } + goto loc_exit; + +loc_positive: + if (vuint <= UINT8_MAX) { + type2 = BINN_UINT8; + } else if (vuint <= UINT16_MAX) { + type2 = BINN_UINT16; + } else if (vuint <= UINT32_MAX) { + type2 = BINN_UINT32; + } + +loc_exit: + pvalue = (char*) psource; + if ((type2) && (type2 != type)) { + *ptype = type2; + storage_type2 = binn_get_write_storage(type2); + *pstorage_type = storage_type2; +#if __BYTE_ORDER == __BIG_ENDIAN + size1 = get_storage_size(storage_type); + size2 = get_storage_size(storage_type2); + pvalue += (size1 - size2); +#endif + } + return pvalue; +} + +BINN_PRIVATE int type_family(int type); + +BINN_PRIVATE BOOL AddValue(binn *item, int type, void *pvalue, int size) { + int32_t argsz, storage_type, extra_type; + uint16_t su; + uint32_t lu; + uint64_t llu; + + unsigned char *p, *ptr; + + binn_get_type_info(type, &storage_type, &extra_type); + + if (pvalue == NULL) { + switch (storage_type) { + case BINN_STORAGE_NOBYTES: + break; + case BINN_STORAGE_BLOB: + case BINN_STORAGE_STRING: + if (size == 0) { + break; // the 2 above are allowed to have 0 length + } + default: + return FALSE; + } + } + + if (type_family(type) == BINN_FAMILY_INT) { + pvalue = compress_int(&storage_type, &type, pvalue); + } + + switch (storage_type) { + case BINN_STORAGE_NOBYTES: + size = 0; + argsz = size; + break; + case BINN_STORAGE_BYTE: + size = 1; + argsz = size; + break; + case BINN_STORAGE_WORD: + size = 2; + argsz = size; + break; + case BINN_STORAGE_DWORD: + size = 4; + argsz = size; + break; + case BINN_STORAGE_QWORD: + size = 8; + argsz = size; + break; + case BINN_STORAGE_BLOB: + if (size < 0) { + return FALSE; + } + //if (size == 0) ... + argsz = size + 4; + break; + case BINN_STORAGE_STRING: + if (size < 0) { + return FALSE; + } + if (size == 0) { + size = strlen2((char*) pvalue); + } + argsz = size + 5; // at least this size + break; + case BINN_STORAGE_CONTAINER: + if (size <= 0) { + return FALSE; + } + argsz = size; + break; + default: + return FALSE; + } + + argsz += 2; // at least 2 bytes used for data_type. + if (CheckAllocation(item, argsz) == FALSE) { + return FALSE; + } + + // Gets the pointer to the next place in buffer + p = ((unsigned char*) item->pbuf) + item->used_size; + + // If the data is not a container, store the data type + if (storage_type != BINN_STORAGE_CONTAINER) { + ptr = (unsigned char*) &type; + if (type > 255) { + type = tobe16(type); // correct the endianess, if needed + *p = *ptr; + p++; + item->used_size++; + ptr++; + } + *p = *ptr; + p++; + item->used_size++; + } + + switch (storage_type) { + case BINN_STORAGE_NOBYTES: + // Nothing to do. + break; + case BINN_STORAGE_BYTE: + *((char*) p) = *((char*) pvalue); + item->used_size += 1; + break; + case BINN_STORAGE_WORD: + su = *((uint16_t*) pvalue); + su = tobe16(su); + memcpy(p, &su, 2); + item->used_size += 2; + break; + case BINN_STORAGE_DWORD: + lu = *((uint32_t*) pvalue); + lu = tobe32(lu); + memcpy(p, &lu, 4); + item->used_size += 4; + break; + case BINN_STORAGE_QWORD: + // is there an htond or htonq to be used with qwords? (64 bits) + llu = *((uint64_t*) pvalue); + llu = tobe64(llu); + memcpy(p, &llu, 8); + item->used_size += 8; + break; + case BINN_STORAGE_BLOB: + lu = tobe32(size); + memcpy(p, &lu, 4); + p += 4; + memcpy(p, pvalue, size); + item->used_size += 4 + size; + break; + case BINN_STORAGE_STRING: + if (size > 127) { + lu = size | 0x80000000; + lu = tobe32(lu); + memcpy(p, &lu, 4); + p += 4; + item->used_size += 4; + } else { + *((unsigned char*) p) = size; + p++; + item->used_size++; + } + memcpy(p, pvalue, size); + p += size; + *((char*) p) = (char) 0; + size++; // null terminator + item->used_size += size; + break; + case BINN_STORAGE_CONTAINER: + memcpy(p, pvalue, size); + item->used_size += size; + break; + } + item->dirty = TRUE; + return TRUE; +} + +BOOL binn_save_header(binn *item) { + unsigned char byte, *p; + int int32, size; + if (item == NULL) { + return FALSE; + } + +#ifndef BINN_DISABLE_SMALL_HEADER + + p = ((unsigned char*) item->pbuf) + MAX_BINN_HEADER; + size = item->used_size - MAX_BINN_HEADER + 3; // at least 3 bytes for the header + + // write the count + if (item->count > 127) { + p -= 4; + size += 3; + int32 = item->count | 0x80000000; + int32 = tobe32(int32); + memcpy(p, &int32, 4); + } else { + p--; + *p = (unsigned char) item->count; + } + + // write the size + if (size > 127) { + p -= 4; + size += 3; + int32 = size | 0x80000000; + int32 = tobe32(int32); + memcpy(p, &int32, 4); + } else { + p--; + *p = (unsigned char) size; + } + + // write the type. + p--; + *p = (unsigned char) item->type; + + // set the values + item->ptr = p; + item->size = size; + + UNUSED(byte); + +#else + + p = (unsigned char*) item->pbuf; + + // write the type. + byte = item->type; + *p = byte; + p++; + // write the size + int32 = item->used_size | 0x80000000; + int32 = tobe32(int32); + memcpy(p, &int32, 4); + p += 4; + // write the count + int32 = item->count | 0x80000000; + int32 = tobe32(int32); + memcpy(p, &int32, 4); + + item->ptr = item->pbuf; + item->size = item->used_size; + +#endif + + item->dirty = FALSE; + return TRUE; +} + +void binn_free(binn *item) { + if (item == NULL) { + return; + } + if (item->userdata_freefn) { + item->userdata_freefn(item->user_data); + item->userdata_freefn = 0; + } + if ((item->writable) && (item->pre_allocated == FALSE)) { + free_fn(item->pbuf); + } + if (item->freefn) { + item->freefn(item->ptr); + } + if (item->allocated) { + free_fn(item); + } else { + memset(item, 0, sizeof(binn)); + item->header = BINN_MAGIC; + } +} + +// free the binn structure but keeps the binn buffer allocated, returning a pointer to it. use the free function to +// release the buffer later +void *binn_release(binn *item) { + void *data; + if (item == NULL) { + return NULL; + } + data = binn_ptr(item); + if (data > item->pbuf) { + memmove(item->pbuf, data, item->size); + data = item->pbuf; + } + if (item->allocated) { + free_fn(item); + } else { + memset(item, 0, sizeof(binn)); + item->header = BINN_MAGIC; + } + return data; +} + +BINN_PRIVATE BOOL IsValidBinnHeader(const void *pbuf, int *ptype, int *pcount, int *psize, int *pheadersize) { + const unsigned char *p, *plimit = 0; + unsigned char byte; + int int32, type, size, count; + if (pbuf == NULL) { + return FALSE; + } + p = (const unsigned char*) pbuf; + if (psize && (*psize > 0)) { + plimit = p + *psize - 1; + } + // get the type + byte = *p; + p++; + if ((byte & BINN_STORAGE_MASK) != BINN_STORAGE_CONTAINER) { + return FALSE; + } + if (byte & BINN_STORAGE_HAS_MORE) { + return FALSE; + } + type = byte; + + switch (type) { + case BINN_LIST: + case BINN_MAP: + case BINN_OBJECT: + break; + default: + return FALSE; + } + + // get the size + if (plimit && (p > plimit)) { + return FALSE; + } + int32 = *((const unsigned char*) p); + if (int32 & 0x80) { + if (plimit && (p + sizeof(int) - 1 > plimit)) { + return FALSE; + } + memcpy(&int32, p, 4); + p += 4; + int32 = frombe32(int32); + int32 &= 0x7FFFFFFF; + } else { + p++; + } + size = int32; + + // get the count + if (plimit && (p > plimit)) { + return FALSE; + } + int32 = *((const unsigned char*) p); + if (int32 & 0x80) { + if (plimit && (p + sizeof(int) - 1 > plimit)) { + return FALSE; + } + memcpy(&int32, p, 4); + p += 4; + int32 = frombe32(int32); + int32 &= 0x7FFFFFFF; + } else { + p++; + } + count = int32; + + if (size < MIN_BINN_SIZE) { + return FALSE; + } + // return the values + if (ptype) { + *ptype = type; + } + if (pcount) { + *pcount = count; + } + if (psize && (*psize == 0)) { + *psize = size; + } + if (pheadersize) { + *pheadersize = (int) (p - (const unsigned char*) pbuf); + } + return TRUE; +} + +binn *binn_copy(void *old) { + int type, count, size, header_size; + unsigned char *old_ptr = binn_ptr(old); + binn *item; + size = 0; + if (!IsValidBinnHeader(old_ptr, &type, &count, &size, &header_size)) { + return NULL; + } + item = binn_new(type, size - header_size + MAX_BINN_HEADER, NULL); + if (item) { + unsigned char *dest; + dest = ((unsigned char*) item->pbuf) + MAX_BINN_HEADER; + memcpy(dest, old_ptr + header_size, size - header_size); + item->used_size = MAX_BINN_HEADER + size - header_size; + item->count = count; + } + return item; +} + +BOOL binn_is_valid_header(const void *pbuf, int *ptype, int *pcount, int *psize, int *pheadersize) { + return IsValidBinnHeader(pbuf, ptype, pcount, psize, pheadersize); +} + +int binn_buf_type(const void *pbuf) { + int type; + if (!IsValidBinnHeader(pbuf, &type, NULL, NULL, NULL)) { + return INVALID_BINN; + } + return type; +} + +int binn_buf_count(const void *pbuf) { + int nitems; + if (!IsValidBinnHeader(pbuf, NULL, &nitems, NULL, NULL)) { + return 0; + } + return nitems; +} + +int binn_buf_size(const void *pbuf) { + int size = 0; + if (!IsValidBinnHeader(pbuf, NULL, NULL, &size, NULL)) { + return 0; + } + return size; +} + +void *binn_ptr(void *ptr) { + binn *item; + switch (binn_get_ptr_type(ptr)) { + case BINN_STRUCT: + item = (binn*) ptr; + if (item->writable && item->dirty) { + binn_save_header(item); + } + return item->ptr; + case BINN_BUFFER: + return ptr; + default: + return NULL; + } +} + +int binn_size(void *ptr) { + binn *item; + switch (binn_get_ptr_type(ptr)) { + case BINN_STRUCT: + item = (binn*) ptr; + if (item->writable && item->dirty) { + binn_save_header(item); + } + return item->size; + case BINN_BUFFER: + return binn_buf_size(ptr); + default: + return 0; + } +} + +int binn_type(void *ptr) { + binn *item; + switch (binn_get_ptr_type(ptr)) { + case BINN_STRUCT: + item = (binn*) ptr; + return item->type; + case BINN_BUFFER: + return binn_buf_type(ptr); + default: + return -1; + } +} + +int binn_count(void *ptr) { + binn *item; + switch (binn_get_ptr_type(ptr)) { + case BINN_STRUCT: + item = (binn*) ptr; + return item->count; + case BINN_BUFFER: + return binn_buf_count(ptr); + default: + return -1; + } +} + +BOOL binn_is_valid_ex(void *ptr, int *ptype, int *pcount, int *psize) { + int i, type, count, size, header_size; + unsigned char *p, *plimit, *base, len; + void *pbuf; + + pbuf = binn_ptr(ptr); + if (pbuf == NULL) { + return FALSE; + } + + // is there an informed size? + if (psize && (*psize > 0)) { + size = *psize; + } else { + size = 0; + } + if (!IsValidBinnHeader(pbuf, &type, &count, &size, &header_size)) { + return FALSE; + } + // is there an informed size? + if (psize && (*psize > 0)) { + // is it the same as the one in the buffer? + if (size != *psize) { + return FALSE; + } + } + // is there an informed count? + if (pcount && (*pcount > 0)) { + // is it the same as the one in the buffer? + if (count != *pcount) { + return FALSE; + } + } + // is there an informed type? + if (ptype && (*ptype != 0)) { + // is it the same as the one in the buffer? + if (type != *ptype) { + return FALSE; + } + } + // it could compare the content size with the size informed on the header + + p = (unsigned char*) pbuf; + base = p; + plimit = p + size; + p += header_size; + + // process all the arguments. + for (i = 0; i < count; i++) { + switch (type) { + case BINN_OBJECT: + // gets the string size (argument name) + len = *p; + p++; + //if (len == 0) goto Invalid; + // increment the used space + p += len; + break; + case BINN_MAP: + // increment the used space + p += 4; + break; + //case BINN_LIST: + // break; + } + // xxx + p = AdvanceDataPos(p, plimit); + if ((p == 0) || (p < base)) { + goto Invalid; + } + } + + if (ptype && (*ptype == 0)) { + *ptype = type; + } + if (pcount && (*pcount == 0)) { + *pcount = count; + } + if (psize && (*psize == 0)) { + *psize = size; + } + return TRUE; + +Invalid: + return FALSE; +} + +BOOL binn_is_valid(void *ptr, int *ptype, int *pcount, int *psize) { + if (ptype) { + *ptype = 0; + } + if (pcount) { + *pcount = 0; + } + if (psize) { + *psize = 0; + } + return binn_is_valid_ex(ptr, ptype, pcount, psize); +} + +/*** INTERNAL FUNCTIONS ****************************************************/ + +BINN_PRIVATE BOOL GetValue(unsigned char *p, binn *value) { + unsigned char byte; + int data_type, storage_type; //, extra_type; + int datasz; + void *p2; + + if (value == NULL) { + return FALSE; + } + memset(value, 0, sizeof(binn)); + value->header = BINN_MAGIC; + + // saves for use with BINN_STORAGE_CONTAINER + p2 = p; + // read the data type + byte = *p; + p++; + storage_type = byte & BINN_STORAGE_MASK; + if (byte & BINN_STORAGE_HAS_MORE) { + data_type = byte << 8; + byte = *p; + p++; + data_type |= byte; + //extra_type = data_type & BINN_TYPE_MASK16; + } else { + data_type = byte; + //extra_type = byte & BINN_TYPE_MASK; + } + + //value->storage_type = storage_type; + value->type = data_type; + + switch (storage_type) { + case BINN_STORAGE_NOBYTES: + break; + case BINN_STORAGE_BYTE: + value->vuint8 = *((unsigned char*) p); + value->ptr = p; //value->ptr = &value->vuint8; + break; + case BINN_STORAGE_WORD: + memcpy(&value->vint16, p, 2); + value->vint16 = frombe16(value->vint16); + value->ptr = &value->vint16; + break; + case BINN_STORAGE_DWORD: + memcpy(&value->vint32, p, 4); + value->vint32 = frombe32(value->vint32); + value->ptr = &value->vint32; + break; + case BINN_STORAGE_QWORD: + memcpy(&value->vint64, p, 8); + value->vint64 = frombe64(value->vint64); + value->ptr = &value->vint64; + break; + case BINN_STORAGE_BLOB: + memcpy(&value->size, p, 4); + p += 4; + value->size = frombe32(value->size); + value->ptr = p; + break; + case BINN_STORAGE_CONTAINER: + value->ptr = p2; // <-- it returns the pointer to the container, not the data + if (IsValidBinnHeader(p2, NULL, &value->count, &value->size, NULL) == FALSE) { + return FALSE; + } + break; + case BINN_STORAGE_STRING: + datasz = *((unsigned char*) p); + if (datasz & 0x80) { + memcpy(&datasz, p, 4); + p += 4; + datasz = frombe32(datasz); + datasz &= 0x7FFFFFFF; + } else { + p++; + } + value->size = datasz; + value->ptr = p; + break; + default: + return FALSE; + } + + // convert the returned value, if needed + switch (value->type) { + case BINN_TRUE: + value->type = BINN_BOOL; + value->vbool = TRUE; + value->ptr = &value->vbool; + break; + case BINN_FALSE: + value->type = BINN_BOOL; + value->vbool = FALSE; + value->ptr = &value->vbool; + break; +#ifdef BINN_EXTENDED + case BINN_SINGLE_STR: + value->type = BINN_SINGLE; + value->vfloat = (float) atof((const char*) value->ptr); // converts from string to double, and then to float + value->ptr = &value->vfloat; + break; + case BINN_DOUBLE_STR: + value->type = BINN_DOUBLE; + value->vdouble = atof((const char*) value->ptr); // converts from string to double + value->ptr = &value->vdouble; + break; +#endif + /* + case BINN_DECIMAL: + case BINN_CURRENCYSTR: + case BINN_DATE: + case BINN_DATETIME: + case BINN_TIME: + */ + } + return TRUE; +} + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +// on little-endian devices we store the value so we can return a pointer to integers. +// it's valid only for single-threaded apps. multi-threaded apps must use the _get_ functions instead. + +binn local_value; + +BINN_PRIVATE void *store_value(binn *value) { + memcpy(&local_value, value, sizeof(binn)); + switch (binn_get_read_storage(value->type)) { + case BINN_STORAGE_NOBYTES: + // return a valid pointer + case BINN_STORAGE_WORD: + case BINN_STORAGE_DWORD: + case BINN_STORAGE_QWORD: + return &local_value.vint32; // returns the pointer to the converted value, from big-endian to little-endian + } + return value->ptr; // returns from the on stack value to be thread-safe (for list, map, object, string and blob) +} + +#endif + +/*** READ FUNCTIONS ********************************************************/ + +BOOL binn_object_get_value(void *ptr, const char *key, binn *value) { + int type, count, size = 0, header_size; + unsigned char *p; + + ptr = binn_ptr(ptr); + if ((ptr == 0) || (key == 0) || (value == 0)) { + return FALSE; + } + + // check the header + if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) { + return FALSE; + } + + if (type != BINN_OBJECT) { + return FALSE; + } + if (count == 0) { + return FALSE; + } + + p = (unsigned char*) ptr; + p = SearchForKey(p, header_size, size, count, key, strlen(key)); + if (p == FALSE) { + return FALSE; + } + return GetValue(p, value); +} + +BOOL binn_map_get_value(void *ptr, int id, binn *value) { + int type, count, size = 0, header_size; + unsigned char *p; + + ptr = binn_ptr(ptr); + if ((ptr == 0) || (value == 0)) { + return FALSE; + } + + // check the header + if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) { + return FALSE; + } + + if (type != BINN_MAP) { + return FALSE; + } + if (count == 0) { + return FALSE; + } + + p = (unsigned char*) ptr; + p = SearchForID(p, header_size, size, count, id); + if (p == FALSE) { + return FALSE; + } + return GetValue(p, value); +} + +BOOL binn_list_get_value(void *ptr, int pos, binn *value) { + int i, type, count, size = 0, header_size; + unsigned char *p, *plimit, *base; + + ptr = binn_ptr(ptr); + if ((ptr == 0) || (value == 0)) { + return FALSE; + } + + // check the header + if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) { + return FALSE; + } + + if (type != BINN_LIST) { + return FALSE; + } + if (count == 0) { + return FALSE; + } + if ((pos <= 0) | (pos > count)) { + return FALSE; + } + pos--; // convert from base 1 to base 0 + + p = (unsigned char*) ptr; + base = p; + plimit = p + size; + p += header_size; + + for (i = 0; i < pos; i++) { + p = AdvanceDataPos(p, plimit); + if ((p == 0) || (p < base)) { + return FALSE; + } + } + return GetValue(p, value); +} + +/*** READ PAIR BY POSITION *************************************************/ + +BINN_PRIVATE BOOL binn_read_pair(int expected_type, void *ptr, int pos, int *pid, char *pkey, binn *value) { + int type, count, size = 0, header_size; + int i, int32, id = 0, counter = 0; + unsigned char *p, *plimit, *base, *key = 0, len = 0; + + ptr = binn_ptr(ptr); + + // check the header + if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) { + return FALSE; + } + + if ((type != expected_type) || (count == 0) || (pos < 1) || (pos > count)) { + return FALSE; + } + + p = (unsigned char*) ptr; + base = p; + plimit = p + size - 1; + p += header_size; + + for (i = 0; i < count; i++) { + switch (type) { + case BINN_MAP: + memcpy(&int32, p, 4); + p += 4; + int32 = frombe32(int32); + if (p > plimit) { + return FALSE; + } + id = int32; + break; + case BINN_OBJECT: + len = *p; + p++; + if (p > plimit) { + return FALSE; + } + key = p; + p += len; + if (p > plimit) { + return FALSE; + } + break; + } + counter++; + if (counter == pos) { + goto found; + } + // + p = AdvanceDataPos(p, plimit); + if ((p == 0) || (p < base)) { + return FALSE; + } + } + return FALSE; + +found: + switch (type) { + case BINN_MAP: + if (pid) { + *pid = id; + } + break; + case BINN_OBJECT: + if (pkey) { + memcpy(pkey, key, len); + pkey[len] = 0; + } + break; + } + return GetValue(p, value); +} + +BOOL binn_map_get_pair(void *ptr, int pos, int *pid, binn *value) { + return binn_read_pair(BINN_MAP, ptr, pos, pid, NULL, value); +} + +BOOL binn_object_get_pair(void *ptr, int pos, char *pkey, binn *value) { + return binn_read_pair(BINN_OBJECT, ptr, pos, NULL, pkey, value); +} + +binn *binn_map_pair(void *map, int pos, int *pid) { + binn *value; + value = (binn*) binn_malloc(sizeof(binn)); + if (binn_read_pair(BINN_MAP, map, pos, pid, NULL, value) == FALSE) { + free_fn(value); + return NULL; + } + value->allocated = TRUE; + return value; +} + +binn *binn_object_pair(void *obj, int pos, char *pkey) { + binn *value; + value = (binn*) binn_malloc(sizeof(binn)); + if (binn_read_pair(BINN_OBJECT, obj, pos, NULL, pkey, value) == FALSE) { + free_fn(value); + return NULL; + } + value->allocated = TRUE; + return value; +} + +void *binn_map_read_pair(void *ptr, int pos, int *pid, int *ptype, int *psize) { + binn value; + + if (binn_map_get_pair(ptr, pos, pid, &value) == FALSE) { + return NULL; + } + if (ptype) { + *ptype = value.type; + } + if (psize) { + *psize = value.size; + } +#if __BYTE_ORDER == __LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif +} + +void *binn_object_read_pair(void *ptr, int pos, char *pkey, int *ptype, int *psize) { + binn value; + + if (binn_object_get_pair(ptr, pos, pkey, &value) == FALSE) { + return NULL; + } + if (ptype) { + *ptype = value.type; + } + if (psize) { + *psize = value.size; + } +#if __BYTE_ORDER == __LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif +} + +/*** SEQUENTIAL READ FUNCTIONS *********************************************/ + +BOOL binn_iter_init(binn_iter *iter, void *ptr, int expected_type) { + int type, count, size = 0, header_size; + + ptr = binn_ptr(ptr); + if ((ptr == 0) || (iter == 0)) { + return FALSE; + } + memset(iter, 0, sizeof(binn_iter)); + + // check the header + if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) { + return FALSE; + } + + if (type != expected_type) { + return FALSE; + } + //if (count == 0) return FALSE; -- should not be used + + iter->plimit = (unsigned char*) ptr + size - 1; + iter->pnext = (unsigned char*) ptr + header_size; + iter->count = count; + iter->current = 0; + iter->type = type; + return TRUE; +} + +BOOL binn_list_next(binn_iter *iter, binn *value) { + unsigned char *pnow; + + if ( (iter == 0) + || (iter->pnext == 0) + || (iter->pnext > iter->plimit) + || (iter->current > iter->count) + || (iter->type != BINN_LIST)) { + return FALSE; + } + + iter->current++; + if (iter->current > iter->count) { + return FALSE; + } + + pnow = iter->pnext; + iter->pnext = AdvanceDataPos(pnow, iter->plimit); + if ((iter->pnext != 0) && (iter->pnext < pnow)) { + return FALSE; + } + return GetValue(pnow, value); +} + +BINN_PRIVATE BOOL binn_read_next_pair(int expected_type, binn_iter *iter, int *pid, char *pkey, binn *value) { + int int32, id; + unsigned char *p, *key; + unsigned short len; + + if ( (iter == 0) + || (iter->pnext == 0) + || (iter->pnext > iter->plimit) + || (iter->current > iter->count) + || (iter->type != expected_type)) { + return FALSE; + } + + iter->current++; + if (iter->current > iter->count) { + return FALSE; + } + + p = iter->pnext; + + switch (expected_type) { + case BINN_MAP: + memcpy(&int32, p, 4); + p += 4; + int32 = frombe32(int32); + if (p > iter->plimit) { + return FALSE; + } + id = int32; + if (pid) { + *pid = id; + } + break; + case BINN_OBJECT: + len = *((unsigned char*) p); + p++; + key = p; + p += len; + if (p > iter->plimit) { + return FALSE; + } + if (pkey) { + memcpy(pkey, key, len); + pkey[len] = 0; + } + break; + } + iter->pnext = AdvanceDataPos(p, iter->plimit); + if ((iter->pnext != 0) && (iter->pnext < p)) { + return FALSE; + } + return GetValue(p, value); +} + +BOOL binn_read_next_pair2(int expected_type, binn_iter *iter, int *klidx, char **pkey, binn *value) { + int int32, id; + unsigned char *p, *key; + unsigned short len; + + if ( (iter == 0) + || (iter->pnext == 0) + || (iter->pnext > iter->plimit) + || (iter->current > iter->count) + || (iter->type != expected_type)) { + return FALSE; + } + + iter->current++; + if (iter->current > iter->count) { + return FALSE; + } + if (pkey) { + *pkey = 0; + } + p = iter->pnext; + switch (expected_type) { + case BINN_MAP: + memcpy(&int32, p, 4); + p += 4; + int32 = frombe32(int32); + if (p > iter->plimit) { + return FALSE; + } + id = int32; + if (klidx) { + *klidx = id; + } + break; + case BINN_OBJECT: + len = *p; + p++; + key = p; + p += len; + if (p > iter->plimit) { + return FALSE; + } + if (klidx) { + *klidx = len; + } + if (pkey) { + *pkey = (char*) key; + } + break; + } + iter->pnext = AdvanceDataPos(p, iter->plimit); + if ((iter->pnext != 0) && (iter->pnext < p)) { + return FALSE; + } + return GetValue(p, value); +} + +BOOL binn_map_next(binn_iter *iter, int *pid, binn *value) { + return binn_read_next_pair(BINN_MAP, iter, pid, NULL, value); +} + +BOOL binn_object_next(binn_iter *iter, char *pkey, binn *value) { + return binn_read_next_pair(BINN_OBJECT, iter, NULL, pkey, value); +} + +BOOL binn_object_next2(binn_iter *iter, char **pkey, int *klen, binn *value) { + return binn_read_next_pair2(BINN_OBJECT, iter, klen, pkey, value); +} + +binn *binn_list_next_value(binn_iter *iter) { + binn *value; + value = (binn*) binn_malloc(sizeof(binn)); + if (binn_list_next(iter, value) == FALSE) { + free_fn(value); + return NULL; + } + value->allocated = TRUE; + return value; +} + +binn *binn_map_next_value(binn_iter *iter, int *pid) { + binn *value; + value = (binn*) binn_malloc(sizeof(binn)); + if (binn_map_next(iter, pid, value) == FALSE) { + free_fn(value); + return NULL; + } + value->allocated = TRUE; + return value; +} + +binn *binn_object_next_value(binn_iter *iter, char *pkey) { + binn *value; + value = (binn*) binn_malloc(sizeof(binn)); + if (binn_object_next(iter, pkey, value) == FALSE) { + free_fn(value); + return NULL; + } + value->allocated = TRUE; + return value; +} + +void *binn_list_read_next(binn_iter *iter, int *ptype, int *psize) { + binn value; + if (binn_list_next(iter, &value) == FALSE) { + return NULL; + } + if (ptype) { + *ptype = value.type; + } + if (psize) { + *psize = value.size; + } +#if __BYTE_ORDER == __LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif +} + +void *binn_map_read_next(binn_iter *iter, int *pid, int *ptype, int *psize) { + binn value; + if (binn_map_next(iter, pid, &value) == FALSE) { + return NULL; + } + if (ptype) { + *ptype = value.type; + } + if (psize) { + *psize = value.size; + } +#if __BYTE_ORDER == __LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif +} + +void *binn_object_read_next(binn_iter *iter, char *pkey, int *ptype, int *psize) { + binn value; + + if (binn_object_next(iter, pkey, &value) == FALSE) { + return NULL; + } + if (ptype) { + *ptype = value.type; + } + if (psize) { + *psize = value.size; + } +#if __BYTE_ORDER == __LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif +} + +/****** EXTENDED INTERFACE ***********************************************************/ +/****** none of the functions above call the functions below *************************/ + +int binn_get_write_storage(int type) { + int storage_type; + switch (type) { + case BINN_SINGLE_STR: + case BINN_DOUBLE_STR: + return BINN_STORAGE_STRING; + case BINN_BOOL: + return BINN_STORAGE_NOBYTES; + default: + binn_get_type_info(type, &storage_type, NULL); + return storage_type; + } +} + +int binn_get_read_storage(int type) { + int storage_type; + switch (type) { +#ifdef BINN_EXTENDED + case BINN_SINGLE_STR: + return BINN_STORAGE_DWORD; + case BINN_DOUBLE_STR: + return BINN_STORAGE_QWORD; +#endif + case BINN_BOOL: + case BINN_TRUE: + case BINN_FALSE: + return BINN_STORAGE_DWORD; + default: + binn_get_type_info(type, &storage_type, NULL); + return storage_type; + } +} + +BINN_PRIVATE BOOL GetWriteConvertedData(int *ptype, void **ppvalue, const int *psize) { + int type; + float f1; + double d1; + char pstr[128]; + + UNUSED(pstr); + UNUSED(d1); + UNUSED(f1); + + type = *ptype; + + if (*ppvalue == NULL) { + switch (type) { + case BINN_NULL: + case BINN_TRUE: + case BINN_FALSE: + break; + case BINN_STRING: + case BINN_BLOB: + if (*psize == 0) { + break; + } + default: + return FALSE; + } + } + + switch (type) { +#ifdef BINN_EXTENDED + case BINN_SINGLE: + f1 = **(float**) ppvalue; + d1 = f1; // convert from float (32bits) to double (64bits) + type = BINN_SINGLE_STR; + goto conv_double; + case BINN_DOUBLE: + d1 = **(double**) ppvalue; + type = BINN_DOUBLE_STR; +conv_double: + // the '%.17e' is more precise than the '%g' + snprintf(pstr, 127, "%.17e", d1); + *ppvalue = pstr; + *ptype = type; + break; +#endif + case BINN_DECIMAL: + case BINN_CURRENCYSTR: + case BINN_DATE: + case BINN_DATETIME: + case BINN_TIME: + return TRUE; + break; + + case BINN_BOOL: + if (**((BOOL**) ppvalue) == FALSE) { + type = BINN_FALSE; + } else { + type = BINN_TRUE; + } + *ptype = type; + break; + } + return TRUE; +} + +BINN_PRIVATE int type_family(int type) { + switch (type) { + case BINN_LIST: + case BINN_MAP: + case BINN_OBJECT: + return BINN_FAMILY_BINN; + + case BINN_INT8: + case BINN_INT16: + case BINN_INT32: + case BINN_INT64: + case BINN_UINT8: + case BINN_UINT16: + case BINN_UINT32: + case BINN_UINT64: + return BINN_FAMILY_INT; + + case BINN_FLOAT32: + case BINN_FLOAT64: + //case BINN_SINGLE: + case BINN_SINGLE_STR: + //case BINN_DOUBLE: + case BINN_DOUBLE_STR: + return BINN_FAMILY_FLOAT; + + case BINN_STRING: + case BINN_HTML: + case BINN_CSS: + case BINN_XML: + case BINN_JSON: + case BINN_JAVASCRIPT: + return BINN_FAMILY_STRING; + + case BINN_BLOB: + case BINN_JPEG: + case BINN_GIF: + case BINN_PNG: + case BINN_BMP: + return BINN_FAMILY_BLOB; + + case BINN_DECIMAL: + case BINN_CURRENCY: + case BINN_DATE: + case BINN_TIME: + case BINN_DATETIME: + return BINN_FAMILY_STRING; + + case BINN_BOOL: + return BINN_FAMILY_BOOL; + + case BINN_NULL: + return BINN_FAMILY_NULL; + + default: + // if it wasn't found + return BINN_FAMILY_NONE; + } +} + +BINN_PRIVATE int int_type(int type) { + switch (type) { + case BINN_INT8: + case BINN_INT16: + case BINN_INT32: + case BINN_INT64: + return BINN_SIGNED_INT; + case BINN_UINT8: + case BINN_UINT16: + case BINN_UINT32: + case BINN_UINT64: + return BINN_UNSIGNED_INT; + default: + return 0; + } +} + +BINN_PRIVATE BOOL copy_raw_value(const void *psource, void *pdest, int data_store) { + switch (data_store) { + case BINN_STORAGE_NOBYTES: + break; + case BINN_STORAGE_BYTE: + *((char*) pdest) = *(const char*) psource; + break; + case BINN_STORAGE_WORD: + *((short*) pdest) = *(const short*) psource; + break; + case BINN_STORAGE_DWORD: + *((int*) pdest) = *(const int*) psource; + break; + case BINN_STORAGE_QWORD: + *((uint64*) pdest) = *(const uint64*) psource; + break; + case BINN_STORAGE_BLOB: + case BINN_STORAGE_STRING: + case BINN_STORAGE_CONTAINER: + *((const char**) pdest) = (const char*) psource; + break; + default: + return FALSE; + } + return TRUE; +} + +BINN_PRIVATE BOOL copy_int_value(void *psource, void *pdest, int source_type, int dest_type) { + uint64 vuint64 = 0; + int64 vf64 = 0; + switch (source_type) { + case BINN_INT8: + vf64 = *(signed char*) psource; + break; + case BINN_INT16: + vf64 = *(short*) psource; + break; + case BINN_INT32: + vf64 = *(int*) psource; + break; + case BINN_INT64: + vf64 = *(int64*) psource; + break; + case BINN_UINT8: + vuint64 = *(unsigned char*) psource; + break; + case BINN_UINT16: + vuint64 = *(unsigned short*) psource; + break; + case BINN_UINT32: + vuint64 = *(unsigned int*) psource; + break; + case BINN_UINT64: + vuint64 = *(uint64*) psource; + break; + default: + return FALSE; + } + // copy from int64 to uint64, if possible + if ((int_type(source_type) == BINN_UNSIGNED_INT) && (int_type(dest_type) == BINN_SIGNED_INT)) { + if (vuint64 > INT64_MAX) { + return FALSE; + } + vf64 = vuint64; + } else if ((int_type(source_type) == BINN_SIGNED_INT) && (int_type(dest_type) == BINN_UNSIGNED_INT)) { + if (vf64 < 0) { + return FALSE; + } + vuint64 = (uint64) vf64; + } + switch (dest_type) { + case BINN_INT8: + if ((vf64 < INT8_MIN) || (vf64 > INT8_MAX)) { + return FALSE; + } + *(signed char*) pdest = (signed char) vf64; + break; + case BINN_INT16: + if ((vf64 < INT16_MIN) || (vf64 > INT16_MAX)) { + return FALSE; + } + *(short*) pdest = (short) vf64; + break; + case BINN_INT32: + if ((vf64 < INT32_MIN) || (vf64 > INT32_MAX)) { + return FALSE; + } + *(int*) pdest = (int) vf64; + break; + case BINN_INT64: + *(int64*) pdest = vf64; + break; + case BINN_UINT8: + if (vuint64 > UINT8_MAX) { + return FALSE; + } + *(unsigned char*) pdest = (unsigned char) vuint64; + break; + case BINN_UINT16: + if (vuint64 > UINT16_MAX) { + return FALSE; + } + *(unsigned short*) pdest = (unsigned short) vuint64; + break; + case BINN_UINT32: + if (vuint64 > UINT32_MAX) { + return FALSE; + } + *(unsigned int*) pdest = (unsigned int) vuint64; + break; + case BINN_UINT64: + *(uint64*) pdest = vuint64; + break; + default: + return FALSE; + } + return TRUE; +} + +#ifdef IW_TESTS + +BOOL copy_int_value_tests(void *psource, void *pdest, int source_type, int dest_type) { + return copy_int_value(psource, pdest, source_type, dest_type); +} + +#endif + +BINN_PRIVATE BOOL copy_float_value(void *psource, void *pdest, int source_type, int dest_type) { + switch (source_type) { + case BINN_FLOAT32: + *(double*) pdest = *(float*) psource; + break; + case BINN_FLOAT64: + *(float*) pdest = (float) *(double*) psource; + break; + default: + return FALSE; + } + return TRUE; +} + +BINN_PRIVATE void zero_value(void *pvalue, int type) { + switch (binn_get_read_storage(type)) { + case BINN_STORAGE_NOBYTES: + break; + case BINN_STORAGE_BYTE: + memset(pvalue, 0, 1); + break; + case BINN_STORAGE_WORD: + memset(pvalue, 0, 2); + break; + case BINN_STORAGE_DWORD: + memset(pvalue, 0, 4); + break; + case BINN_STORAGE_QWORD: + memset(pvalue, 0, 8); + break; + case BINN_STORAGE_BLOB: + case BINN_STORAGE_STRING: + case BINN_STORAGE_CONTAINER: + *(char**) pvalue = NULL; + break; + } +} + +BINN_PRIVATE BOOL copy_value(void *psource, void *pdest, int source_type, int dest_type, int data_store) { + if (type_family(source_type) != type_family(dest_type)) { + return FALSE; + } + if ((type_family(source_type) == BINN_FAMILY_INT) && (source_type != dest_type)) { + return copy_int_value(psource, pdest, source_type, dest_type); + } else if ((type_family(source_type) == BINN_FAMILY_FLOAT) && (source_type != dest_type)) { + return copy_float_value(psource, pdest, source_type, dest_type); + } else { + return copy_raw_value(psource, pdest, data_store); + } +} + +/*** WRITE FUNCTIONS *****************************************************************/ + +BOOL binn_list_add(binn *list, int type, void *pvalue, int size) { + if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) { + return FALSE; + } + return binn_list_add_raw(list, type, pvalue, size); +} + +BOOL binn_map_set(binn *map, int id, int type, void *pvalue, int size) { + if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) { + return FALSE; + } + return binn_map_set_raw(map, id, type, pvalue, size); +} + +BOOL binn_object_set(binn *obj, const char *key, int type, void *pvalue, int size) { + if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) { + return FALSE; + } + return binn_object_set_raw(obj, key, strlen(key), type, pvalue, size); +} + +BOOL binn_object_set2(binn *obj, const char *key, int keylen, int type, void *pvalue, int size) { + if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) { + return FALSE; + } + return binn_object_set_raw(obj, key, keylen, type, pvalue, size); +} + +// this function is used by the wrappers +BOOL binn_add_value(binn *item, int binn_type, int id, char *name, int type, void *pvalue, int size) { + switch (binn_type) { + case BINN_LIST: + return binn_list_add(item, type, pvalue, size); + case BINN_MAP: + return binn_map_set(item, id, type, pvalue, size); + case BINN_OBJECT: + return binn_object_set(item, name, type, pvalue, size); + default: + return FALSE; + } +} + +BOOL binn_list_add_new(binn *list, binn *value) { + BOOL retval; + retval = binn_list_add_value(list, value); + binn_free(value); + return retval; +} + +BOOL binn_map_set_new(binn *map, int id, binn *value) { + BOOL retval; + retval = binn_map_set_value(map, id, value); + binn_free(value); + return retval; +} + +BOOL binn_object_set_new(binn *obj, const char *key, binn *value) { + BOOL retval; + retval = binn_object_set_value(obj, key, value); + binn_free(value); + return retval; +} + +BOOL binn_object_set_new2(binn *obj, const char *key, int keylen, binn *value) { + BOOL retval; + retval = binn_object_set_value2(obj, key, keylen, value); + binn_free(value); + return retval; +} + +/*** READ FUNCTIONS ******************************************************************/ + +binn *binn_list_value(void *ptr, int pos) { + binn *value; + value = (binn*) binn_malloc(sizeof(binn)); + if (binn_list_get_value(ptr, pos, value) == FALSE) { + free_fn(value); + return NULL; + } + value->allocated = TRUE; + return value; +} + +binn *binn_map_value(void *ptr, int id) { + binn *value; + value = (binn*) binn_malloc(sizeof(binn)); + if (binn_map_get_value(ptr, id, value) == FALSE) { + free_fn(value); + return NULL; + } + value->allocated = TRUE; + return value; +} + +binn *binn_object_value(void *ptr, const char *key) { + binn *value; + value = (binn*) binn_malloc(sizeof(binn)); + if (binn_object_get_value(ptr, key, value) == FALSE) { + free_fn(value); + return NULL; + } + value->allocated = TRUE; + return value; +} + +void *binn_list_read(void *list, int pos, int *ptype, int *psize) { + binn value; + if (binn_list_get_value(list, pos, &value) == FALSE) { + return NULL; + } + if (ptype) { + *ptype = value.type; + } + if (psize) { + *psize = value.size; + } +#if __BYTE_ORDER == __LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif +} + +void *binn_map_read(void *map, int id, int *ptype, int *psize) { + binn value; + if (binn_map_get_value(map, id, &value) == FALSE) { + return NULL; + } + if (ptype) { + *ptype = value.type; + } + if (psize) { + *psize = value.size; + } +#if __BYTE_ORDER == __LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif +} + +void *binn_object_read(void *obj, const char *key, int *ptype, int *psize) { + binn value; + if (binn_object_get_value(obj, key, &value) == FALSE) { + return NULL; + } + if (ptype) { + *ptype = value.type; + } + if (psize) { + *psize = value.size; + } +#if __BYTE_ORDER == __LITTLE_ENDIAN + return store_value(&value); +#else + return value.ptr; +#endif +} + +BOOL binn_list_get(void *ptr, int pos, int type, void *pvalue, int *psize) { + binn value; + int storage_type; + storage_type = binn_get_read_storage(type); + if ((storage_type != BINN_STORAGE_NOBYTES) && (pvalue == NULL)) { + return FALSE; + } + zero_value(pvalue, type); + if (binn_list_get_value(ptr, pos, &value) == FALSE) { + return FALSE; + } + if (copy_value(value.ptr, pvalue, value.type, type, storage_type) == FALSE) { + return FALSE; + } + if (psize) { + *psize = value.size; + } + return TRUE; +} + +BOOL binn_map_get(void *ptr, int id, int type, void *pvalue, int *psize) { + binn value; + int storage_type; + storage_type = binn_get_read_storage(type); + if ((storage_type != BINN_STORAGE_NOBYTES) && (pvalue == NULL)) { + return FALSE; + } + zero_value(pvalue, type); + if (binn_map_get_value(ptr, id, &value) == FALSE) { + return FALSE; + } + if (copy_value(value.ptr, pvalue, value.type, type, storage_type) == FALSE) { + return FALSE; + } + if (psize) { + *psize = value.size; + } + return TRUE; +} + +BOOL binn_object_get(void *ptr, const char *key, int type, void *pvalue, int *psize) { + binn value; + int storage_type; + storage_type = binn_get_read_storage(type); + if ((storage_type != BINN_STORAGE_NOBYTES) && (pvalue == NULL)) { + return FALSE; + } + zero_value(pvalue, type); + if (binn_object_get_value(ptr, key, &value) == FALSE) { + return FALSE; + } + if (copy_value(value.ptr, pvalue, value.type, type, storage_type) == FALSE) { + return FALSE; + } + if (psize) { + *psize = value.size; + } + return TRUE; +} + +// these functions below may not be implemented as inline functions, because +// they use a lot of space, even for the variable. so they will be exported. + +// but what about using as static? +// is there any problem with wrappers? can these wrappers implement these functions using the header? +// if as static, will they be present even on modules that don't use the functions? + +signed char binn_list_int8(void *list, int pos) { + signed char value; + binn_list_get(list, pos, BINN_INT8, &value, NULL); + return value; +} + +short binn_list_int16(void *list, int pos) { + short value; + binn_list_get(list, pos, BINN_INT16, &value, NULL); + return value; +} + +int binn_list_int32(void *list, int pos) { + int value; + binn_list_get(list, pos, BINN_INT32, &value, NULL); + return value; +} + +int64 binn_list_int64(void *list, int pos) { + int64 value; + binn_list_get(list, pos, BINN_INT64, &value, NULL); + return value; +} + +unsigned char binn_list_uint8(void *list, int pos) { + unsigned char value; + binn_list_get(list, pos, BINN_UINT8, &value, NULL); + return value; +} + +unsigned short binn_list_uint16(void *list, int pos) { + unsigned short value; + binn_list_get(list, pos, BINN_UINT16, &value, NULL); + return value; +} + +unsigned int binn_list_uint32(void *list, int pos) { + unsigned int value; + binn_list_get(list, pos, BINN_UINT32, &value, NULL); + return value; +} + +uint64 binn_list_uint64(void *list, int pos) { + uint64 value; + binn_list_get(list, pos, BINN_UINT64, &value, NULL); + return value; +} + +float binn_list_float(void *list, int pos) { + float value; + binn_list_get(list, pos, BINN_FLOAT32, &value, NULL); + return value; +} + +double binn_list_double(void *list, int pos) { + double value; + binn_list_get(list, pos, BINN_FLOAT64, &value, NULL); + return value; +} + +BOOL binn_list_bool(void *list, int pos) { + BOOL value; + binn_list_get(list, pos, BINN_BOOL, &value, NULL); + return value; +} + +BOOL binn_list_null(void *list, int pos) { + return binn_list_get(list, pos, BINN_NULL, NULL, NULL); +} + +char *binn_list_str(void *list, int pos) { + char *value; + binn_list_get(list, pos, BINN_STRING, &value, NULL); + return value; +} + +void *binn_list_blob(void *list, int pos, int *psize) { + void *value; + binn_list_get(list, pos, BINN_BLOB, &value, psize); + return value; +} + +void *binn_list_list(void *list, int pos) { + void *value; + binn_list_get(list, pos, BINN_LIST, &value, NULL); + return value; +} + +void *binn_list_map(void *list, int pos) { + void *value; + binn_list_get(list, pos, BINN_MAP, &value, NULL); + return value; +} + +void *binn_list_object(void *list, int pos) { + void *value; + binn_list_get(list, pos, BINN_OBJECT, &value, NULL); + return value; +} + +signed char binn_map_int8(void *map, int id) { + signed char value; + binn_map_get(map, id, BINN_INT8, &value, NULL); + return value; +} + +short binn_map_int16(void *map, int id) { + short value; + binn_map_get(map, id, BINN_INT16, &value, NULL); + return value; +} + +int binn_map_int32(void *map, int id) { + int value; + binn_map_get(map, id, BINN_INT32, &value, NULL); + return value; +} + +int64 binn_map_int64(void *map, int id) { + int64 value; + binn_map_get(map, id, BINN_INT64, &value, NULL); + return value; +} + +unsigned char binn_map_uint8(void *map, int id) { + unsigned char value; + binn_map_get(map, id, BINN_UINT8, &value, NULL); + return value; +} + +unsigned short binn_map_uint16(void *map, int id) { + unsigned short value; + binn_map_get(map, id, BINN_UINT16, &value, NULL); + return value; +} + +unsigned int binn_map_uint32(void *map, int id) { + unsigned int value; + binn_map_get(map, id, BINN_UINT32, &value, NULL); + return value; +} + +uint64 binn_map_uint64(void *map, int id) { + uint64 value; + binn_map_get(map, id, BINN_UINT64, &value, NULL); + return value; +} + +float binn_map_float(void *map, int id) { + float value; + binn_map_get(map, id, BINN_FLOAT32, &value, NULL); + return value; +} + +double binn_map_double(void *map, int id) { + double value; + binn_map_get(map, id, BINN_FLOAT64, &value, NULL); + return value; +} + +BOOL binn_map_bool(void *map, int id) { + BOOL value; + binn_map_get(map, id, BINN_BOOL, &value, NULL); + return value; +} + +BOOL binn_map_null(void *map, int id) { + return binn_map_get(map, id, BINN_NULL, NULL, NULL); +} + +char *binn_map_str(void *map, int id) { + char *value; + binn_map_get(map, id, BINN_STRING, &value, NULL); + return value; +} + +void *binn_map_blob(void *map, int id, int *psize) { + void *value; + binn_map_get(map, id, BINN_BLOB, &value, psize); + return value; +} + +void *binn_map_list(void *map, int id) { + void *value; + binn_map_get(map, id, BINN_LIST, &value, NULL); + return value; +} + +void *binn_map_map(void *map, int id) { + void *value; + binn_map_get(map, id, BINN_MAP, &value, NULL); + return value; +} + +void *binn_map_object(void *map, int id) { + void *value; + binn_map_get(map, id, BINN_OBJECT, &value, NULL); + return value; +} + +signed char binn_object_int8(void *obj, const char *key) { + signed char value; + binn_object_get(obj, key, BINN_INT8, &value, NULL); + return value; +} + +short binn_object_int16(void *obj, const char *key) { + short value; + binn_object_get(obj, key, BINN_INT16, &value, NULL); + return value; +} + +int binn_object_int32(void *obj, const char *key) { + int value; + binn_object_get(obj, key, BINN_INT32, &value, NULL); + return value; +} + +int64 binn_object_int64(void *obj, const char *key) { + int64 value; + binn_object_get(obj, key, BINN_INT64, &value, NULL); + return value; +} + +unsigned char binn_object_uint8(void *obj, const char *key) { + unsigned char value; + binn_object_get(obj, key, BINN_UINT8, &value, NULL); + return value; +} + +unsigned short binn_object_uint16(void *obj, const char *key) { + unsigned short value; + binn_object_get(obj, key, BINN_UINT16, &value, NULL); + return value; +} + +unsigned int binn_object_uint32(void *obj, const char *key) { + unsigned int value; + binn_object_get(obj, key, BINN_UINT32, &value, NULL); + return value; +} + +uint64 binn_object_uint64(void *obj, const char *key) { + uint64 value; + binn_object_get(obj, key, BINN_UINT64, &value, NULL); + return value; +} + +float binn_object_float(void *obj, const char *key) { + float value; + binn_object_get(obj, key, BINN_FLOAT32, &value, NULL); + return value; +} + +double binn_object_double(void *obj, const char *key) { + double value; + binn_object_get(obj, key, BINN_FLOAT64, &value, NULL); + return value; +} + +BOOL binn_object_bool(void *obj, const char *key) { + BOOL value; + binn_object_get(obj, key, BINN_BOOL, &value, NULL); + return value; +} + +BOOL binn_object_null(void *obj, const char *key) { + return binn_object_get(obj, key, BINN_NULL, NULL, NULL); +} + +char *binn_object_str(void *obj, const char *key) { + char *value; + binn_object_get(obj, key, BINN_STRING, &value, NULL); + return value; +} + +void *binn_object_blob(void *obj, const char *key, int *psize) { + void *value; + binn_object_get(obj, key, BINN_BLOB, &value, psize); + return value; +} + +void *binn_object_list(void *obj, const char *key) { + void *value; + binn_object_get(obj, key, BINN_LIST, &value, NULL); + return value; +} + +void *binn_object_map(void *obj, const char *key) { + void *value; + binn_object_get(obj, key, BINN_MAP, &value, NULL); + return value; +} + +void *binn_object_object(void *obj, const char *key) { + void *value; + binn_object_get(obj, key, BINN_OBJECT, &value, NULL); + return value; +} + +BINN_PRIVATE binn *binn_alloc_item() { + binn *item; + item = (binn*) binn_malloc(sizeof(binn)); + if (item) { + memset(item, 0, sizeof(binn)); + item->header = BINN_MAGIC; + item->allocated = TRUE; + } + return item; +} + +binn *binn_value(int type, void *pvalue, int size, binn_mem_free freefn) { + int storage_type; + binn *item = binn_alloc_item(); + if (item) { + item->type = type; + binn_get_type_info(type, &storage_type, NULL); + switch (storage_type) { + case BINN_STORAGE_NOBYTES: + break; + case BINN_STORAGE_STRING: + if (size == 0) { + size = strlen((const char*) pvalue) + 1; + } + case BINN_STORAGE_BLOB: + case BINN_STORAGE_CONTAINER: + if (freefn == BINN_TRANSIENT) { + item->ptr = binn_memdup(pvalue, size); + if (item->ptr == NULL) { + free_fn(item); + return NULL; + } + item->freefn = free_fn; + if (storage_type == BINN_STORAGE_STRING) { + size--; + } + } else { + item->ptr = pvalue; + item->freefn = freefn; + } + item->size = size; + break; + default: + item->ptr = &item->vint32; + copy_raw_value(pvalue, item->ptr, storage_type); + } + } + return item; +} + +BOOL binn_set_string(binn *item, char *str, binn_mem_free pfree) { + if ((item == NULL) || (str == NULL)) { + return FALSE; + } + if (pfree == BINN_TRANSIENT) { + item->ptr = binn_memdup(str, strlen(str) + 1); + if (item->ptr == NULL) { + return FALSE; + } + item->freefn = free_fn; + } else { + item->ptr = str; + item->freefn = pfree; + } + item->type = BINN_STRING; + return TRUE; +} + +BOOL binn_set_blob(binn *item, void *ptr, int size, binn_mem_free pfree) { + if ((item == NULL) || (ptr == NULL)) { + return FALSE; + } + if (pfree == BINN_TRANSIENT) { + item->ptr = binn_memdup(ptr, size); + if (item->ptr == NULL) { + return FALSE; + } + item->freefn = free_fn; + } else { + item->ptr = ptr; + item->freefn = pfree; + } + item->type = BINN_BLOB; + item->size = size; + return TRUE; +} + +/*** READ CONVERTED VALUE ************************************************************/ + +#ifdef _MSC_VER +#define atoi64 _atoi64 +#else + +int64 atoi64(char *str) { + int64 retval; + int is_negative = 0; + + if (*str == '-') { + is_negative = 1; + str++; + } + retval = 0; + for ( ; *str; str++) { + retval = 10 * retval + (*str - '0'); + } + if (is_negative) { + retval *= -1; + } + return retval; +} + +#endif + +BINN_PRIVATE BOOL is_integer(char *p) { + BOOL retval; + if (p == NULL) { + return FALSE; + } + if (*p == '-') { + p++; + } + if (*p == 0) { + return FALSE; + } + retval = TRUE; + for ( ; *p; p++) { + if ((*p < '0') || (*p > '9')) { + retval = FALSE; + } + } + return retval; +} + +BINN_PRIVATE BOOL is_float(char *p) { + BOOL retval, number_found = FALSE; + if (p == NULL) { + return FALSE; + } + if (*p == '-') { + p++; + } + if (*p == 0) { + return FALSE; + } + retval = TRUE; + for ( ; *p; p++) { + if ((*p == '.') || (*p == ',')) { + if (!number_found) { + retval = FALSE; + } + } else if ((*p >= '0') && (*p <= '9')) { + number_found = TRUE; + } else { + return FALSE; + } + } + return retval; +} + +BINN_PRIVATE BOOL is_bool_str(char *str, BOOL *pbool) { + int64 vint; + double vdouble; + if ((str == NULL) || (pbool == NULL)) { + return FALSE; + } + if (stricmp(str, "true") == 0) { + goto loc_true; + } + if (stricmp(str, "yes") == 0) { + goto loc_true; + } + if (stricmp(str, "on") == 0) { + goto loc_true; + } + //if (stricmp(str, "1") == 0) goto loc_true; + + if (stricmp(str, "false") == 0) { + goto loc_false; + } + if (stricmp(str, "no") == 0) { + goto loc_false; + } + if (stricmp(str, "off") == 0) { + goto loc_false; + } + //if (stricmp(str, "0") == 0) goto loc_false; + + if (is_integer(str)) { + vint = atoi64(str); + *pbool = (vint != 0) ? TRUE : FALSE; + return TRUE; + } else if (is_float(str)) { + vdouble = atof(str); + *pbool = (vdouble != 0) ? TRUE : FALSE; + return TRUE; + } + + return FALSE; + +loc_true: + *pbool = TRUE; + return TRUE; + +loc_false: + *pbool = FALSE; + return TRUE; +} + +BOOL binn_get_int32(binn *value, int *pint) { + if ((value == NULL) || (pint == NULL)) { + return FALSE; + } + if (type_family(value->type) == BINN_FAMILY_INT) { + return copy_int_value(value->ptr, pint, value->type, BINN_INT32); + } + switch (value->type) { + case BINN_FLOAT: + *pint = round(value->vfloat); + break; + case BINN_DOUBLE: + *pint = round(value->vdouble); + break; + case BINN_STRING: + if (is_integer((char*) value->ptr)) { + *pint = atoi((char*) value->ptr); + } else if (is_float((char*) value->ptr)) { + *pint = round(atof((char*) value->ptr)); + } else { + return FALSE; + } + break; + case BINN_BOOL: + *pint = value->vbool; + break; + default: + return FALSE; + } + return TRUE; +} + +BOOL binn_get_int64(binn *value, int64 *pint) { + if ((value == NULL) || (pint == NULL)) { + return FALSE; + } + if (type_family(value->type) == BINN_FAMILY_INT) { + return copy_int_value(value->ptr, pint, value->type, BINN_INT64); + } + switch (value->type) { + case BINN_FLOAT: + *pint = round(value->vfloat); + break; + case BINN_DOUBLE: + *pint = round(value->vdouble); + break; + case BINN_STRING: + if (is_integer((char*) value->ptr)) { + *pint = atoi64((char*) value->ptr); + } else if (is_float((char*) value->ptr)) { + *pint = round(atof((char*) value->ptr)); + } else { + return FALSE; + } + break; + case BINN_BOOL: + *pint = value->vbool; + break; + default: + return FALSE; + } + return TRUE; +} + +BOOL binn_get_double(binn *value, double *pfloat) { + int64 vint; + if ((value == NULL) || (pfloat == NULL)) { + return FALSE; + } + if (type_family(value->type) == BINN_FAMILY_INT) { + if (copy_int_value(value->ptr, &vint, value->type, BINN_INT64) == FALSE) { + return FALSE; + } + *pfloat = (double) vint; + return TRUE; + } + switch (value->type) { + case BINN_FLOAT: + *pfloat = value->vfloat; + break; + case BINN_DOUBLE: + *pfloat = value->vdouble; + break; + case BINN_STRING: + if (is_integer((char*) value->ptr)) { + *pfloat = (double) atoi64((char*) value->ptr); + } else if (is_float((char*) value->ptr)) { + *pfloat = atof((char*) value->ptr); + } else { + return FALSE; + } + break; + case BINN_BOOL: + *pfloat = value->vbool; + break; + default: + return FALSE; + } + return TRUE; +} + +BOOL binn_get_bool(binn *value, BOOL *pbool) { + int64 vint; + if ((value == NULL) || (pbool == NULL)) { + return FALSE; + } + if (type_family(value->type) == BINN_FAMILY_INT) { + if (copy_int_value(value->ptr, &vint, value->type, BINN_INT64) == FALSE) { + return FALSE; + } + *pbool = (vint != 0) ? TRUE : FALSE; + return TRUE; + } + switch (value->type) { + case BINN_BOOL: + *pbool = value->vbool; + break; + case BINN_FLOAT: + *pbool = (value->vfloat != 0) ? TRUE : FALSE; + break; + case BINN_DOUBLE: + *pbool = (value->vdouble != 0) ? TRUE : FALSE; + break; + case BINN_STRING: + return is_bool_str((char*) value->ptr, pbool); + default: + return FALSE; + } + return TRUE; +} + +char *binn_get_str(binn *value) { + int64 vint; + char buf[128]; + if (value == NULL) { + return NULL; + } + if (type_family(value->type) == BINN_FAMILY_INT) { + if (copy_int_value(value->ptr, &vint, value->type, BINN_INT64) == FALSE) { + return NULL; + } + snprintf(buf, sizeof(buf), "%" INT64_FORMAT, vint); // -V576 + goto loc_convert_value; + } + switch (value->type) { + case BINN_FLOAT: + value->vdouble = value->vfloat; + case BINN_DOUBLE: + snprintf(buf, sizeof(buf), "%g", value->vdouble); + goto loc_convert_value; + case BINN_STRING: + return (char*) value->ptr; + case BINN_BOOL: + if (value->vbool) { + strcpy(buf, "true"); + } else { + strcpy(buf, "false"); + } + goto loc_convert_value; + } + return NULL; + +loc_convert_value: + value->ptr = strdup(buf); + if (value->ptr == NULL) { + return NULL; + } + value->freefn = free; + value->type = BINN_STRING; + return (char*) value->ptr; +} + +/*** GENERAL FUNCTIONS ***************************************************************/ + +BOOL binn_is_container(binn *item) { + if (item == NULL) { + return FALSE; + } + switch (item->type) { + case BINN_LIST: + case BINN_MAP: + case BINN_OBJECT: + return TRUE; + default: + return FALSE; + } +} + +void binn_set_user_data(binn *item, void *user_data, binn_user_data_free freefn) { + if (item->userdata_freefn) { + item->userdata_freefn(item->user_data); + } + item->user_data = user_data; + item->userdata_freefn = free_fn; +} diff --git a/src/jbl/binn.h b/src/jbl/binn.h new file mode 100644 index 0000000..8d46fe1 --- /dev/null +++ b/src/jbl/binn.h @@ -0,0 +1,1063 @@ + +#pragma once +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TO ENABLE INLINE FUNCTIONS: +// ON MSVC: enable the 'Inline Function Expansion' (/Ob2) compiler option, and maybe the +// 'Whole Program Optimitazion' (/GL), that requires the +// 'Link Time Code Generation' (/LTCG) linker option to be enabled too + +#ifndef BINN_H +#define BINN_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void*) 0) +#endif +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef BOOL +typedef int BOOL; +#endif + +#ifndef BINN_PRIVATE +#ifdef DEBUG +#define BINN_PRIVATE +#else +#define BINN_PRIVATE static +#endif +#endif + +#ifdef _MSC_VER +#define INLINE __inline +#define ALWAYS_INLINE __forceinline +#else +// you can change to 'extern inline' if using the gcc option -flto +#define INLINE static inline +#define ALWAYS_INLINE static inline __attribute__((always_inline)) +#endif + +#ifndef int64 +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef __int64 int64; +typedef unsigned __int64 uint64; +#else +typedef int64_t int64; +typedef uint64_t uint64; +#endif +#endif + +// BINN CONSTANTS ---------------------------------------- + +// magic number: 0x1F 0xb1 0x22 0x1F => 0x1FB1221F or 0x1F22B11F +// because the BINN_STORAGE_NOBYTES (binary 000) may not have so many sub-types (BINN_STORAGE_HAS_MORE = 0x10) +#define BINN_MAGIC 0x1F22B11F +#define MAX_BINN_HEADER 9 // [1:type][4:size][4:count] +#define MIN_BINN_SIZE 3 // [1:type][1:size][1:count] +#define MAX_BIN_KEY_LEN 255 + +#define INVALID_BINN 0 + +// Storage Data Types ------------------------------------ + +#define BINN_STORAGE_NOBYTES 0x00 +#define BINN_STORAGE_BYTE 0x20 // 8 bits +#define BINN_STORAGE_WORD 0x40 // 16 bits -- the endianess (byte order) is automatically corrected +#define BINN_STORAGE_DWORD 0x60 // 32 bits -- the endianess (byte order) is automatically corrected +#define BINN_STORAGE_QWORD 0x80 // 64 bits -- the endianess (byte order) is automatically corrected +#define BINN_STORAGE_STRING 0xA0 // Are stored with null termination +#define BINN_STORAGE_BLOB 0xC0 +#define BINN_STORAGE_CONTAINER 0xE0 +#define BINN_STORAGE_VIRTUAL 0x80000 +//-- +#define BINN_STORAGE_MIN BINN_STORAGE_NOBYTES +#define BINN_STORAGE_MAX BINN_STORAGE_CONTAINER + +#define BINN_STORAGE_MASK 0xE0 +#define BINN_STORAGE_MASK16 0xE000 +#define BINN_STORAGE_HAS_MORE 0x10 +#define BINN_TYPE_MASK 0x0F +#define BINN_TYPE_MASK16 0x0FFF + +#define BINN_MAX_VALUE_MASK 0xFFFFF +//++ + +// Data Formats ------------------------------------------ + +#define BINN_LIST 0xE0 +#define BINN_MAP 0xE1 +#define BINN_OBJECT 0xE2 + +#define BINN_NULL 0x00 +#define BINN_TRUE 0x01 +#define BINN_FALSE 0x02 + +#define BINN_UINT8 0x20 // (BYTE) (unsigned byte) Is the default format for the BYTE type +#define BINN_INT8 0x21 // (BYTE) (signed byte, from -128 to +127. The 0x80 is the sign bit, so the range in hex is + // from 0x80 [-128] to 0x7F [127], being 0x00 = 0 and 0xFF = -1) +#define BINN_UINT16 0x40 // (WORD) (unsigned integer) Is the default format for the WORD type +#define BINN_INT16 0x41 // (WORD) (signed integer) +#define BINN_UINT32 0x60 // (DWORD) (unsigned integer) Is the default format for the DWORD type +#define BINN_INT32 0x61 // (DWORD) (signed integer) +#define BINN_UINT64 0x80 // (QWORD) (unsigned integer) Is the default format for the QWORD type +#define BINN_INT64 0x81 // (QWORD) (signed integer) + +#define BINN_SCHAR BINN_INT8 +#define BINN_UCHAR BINN_UINT8 + +#define BINN_STRING 0xA0 // (STRING) Raw String +#define BINN_DATETIME 0xA1 // (STRING) iso8601 format -- YYYY-MM-DD HH:MM:SS +#define BINN_DATE 0xA2 // (STRING) iso8601 format -- YYYY-MM-DD +#define BINN_TIME 0xA3 // (STRING) iso8601 format -- HH:MM:SS +#define BINN_DECIMAL 0xA4 // (STRING) High precision number - used for generic decimal values and for those ones + // that cannot be represented in the float64 format. +#define BINN_CURRENCYSTR 0xA5 // (STRING) With currency unit/symbol - check for some iso standard format +#define BINN_SINGLE_STR 0xA6 // (STRING) Can be restored to float32 +#define BINN_DOUBLE_STR 0xA7 // (STRING) May be restored to float64 + +#define BINN_FLOAT32 0x62 // (DWORD) +#define BINN_FLOAT64 0x82 // (QWORD) +#define BINN_FLOAT BINN_FLOAT32 +#define BINN_SINGLE BINN_FLOAT32 +#define BINN_DOUBLE BINN_FLOAT64 + +#define BINN_CURRENCY 0x83 // (QWORD) + +#define BINN_BLOB 0xC0 // (BLOB) Raw Blob + + +// virtual types: + +#define BINN_BOOL 0x80061 // (DWORD) The value may be 0 or 1 + +#ifdef BINN_EXTENDED +//#define BINN_SINGLE 0x800A1 // (STRING) Can be restored to float32 +//#define BINN_DOUBLE 0x800A2 // (STRING) May be restored to float64 +#endif + +//#define BINN_BINN 0x800E1 // (CONTAINER) +//#define BINN_BINN_BUFFER 0x800C1 // (BLOB) user binn. it's not open by the parser + +// extended content types: + +// strings: +#define BINN_HTML 0xB001 +#define BINN_XML 0xB002 +#define BINN_JSON 0xB003 +#define BINN_JAVASCRIPT 0xB004 +#define BINN_CSS 0xB005 + +// blobs: +#define BINN_JPEG 0xD001 +#define BINN_GIF 0xD002 +#define BINN_PNG 0xD003 +#define BINN_BMP 0xD004 + +// type families +#define BINN_FAMILY_NONE 0x00 +#define BINN_FAMILY_NULL 0xf1 +#define BINN_FAMILY_INT 0xf2 +#define BINN_FAMILY_FLOAT 0xf3 +#define BINN_FAMILY_STRING 0xf4 +#define BINN_FAMILY_BLOB 0xf5 +#define BINN_FAMILY_BOOL 0xf6 +#define BINN_FAMILY_BINN 0xf7 + +// integer types related to signal +#define BINN_SIGNED_INT 11 +#define BINN_UNSIGNED_INT 22 + +typedef void (*binn_mem_free)(void*); + +typedef void (*binn_user_data_free)(void*); + +#define BINN_STATIC ((binn_mem_free) 0) +#define BINN_TRANSIENT ((binn_mem_free) - 1) + + +#define BINN_IS_CONTAINER_TYPE(type_) ((type_) >= BINN_LIST && (type_) <= BINN_OBJECT) + +#define BINN_IS_INT_TYPE(type_) ((type_) >= BINN_UINT8 && (type_) <= BINN_INT64) + + +// --- BINN STRUCTURE -------------------------------------------------------------- + +struct binn_struct { + int header; // this struct header holds the magic number (BINN_MAGIC) that identifies this memory block as a + // binn structure + BOOL allocated; // the struct can be allocated using malloc_fn() or can be on the stack + BOOL writable; // did it was create for writing? it can use the pbuf if not unified with ptr + BOOL dirty; // the container header is not written to the buffer + // + void *pbuf; // use *ptr below? + BOOL pre_allocated; + int alloc_size; + int used_size; + // + int type; + void *ptr; + int size; + int count; + // + binn_mem_free freefn; // used only when type == BINN_STRING or BINN_BLOB + // + void *user_data; + binn_user_data_free userdata_freefn; + // + union { + int8_t vint8; + int16_t vint16; + int32_t vint32; + int64_t vint64; + uint8_t vuint8; + uint16_t vuint16; + uint32_t vuint32; + uint64_t vuint64; + // + signed char vchar; + unsigned char vuchar; + signed short vshort; + unsigned short vushort; + signed int vint; + unsigned int vuint; + // + float vfloat; + double vdouble; + // + BOOL vbool; + }; +}; + +typedef struct binn_struct binn; + +// --- GENERAL FUNCTIONS ---------------------------------------------------------- + +void binn_set_alloc_functions( + void*(*new_malloc)(size_t), void*(*new_realloc)(void*, size_t), + void (*new_free)(void*)); +int binn_create_type(int storage_type, int data_type_index); +BOOL binn_get_type_info(int long_type, int *pstorage_type, int *pextra_type); +int binn_get_write_storage(int type); +int binn_get_read_storage(int type); +BOOL binn_is_container(binn *item); + +void binn_set_user_data(binn *item, void *user_data, binn_user_data_free freefn); + +// --- WRITE FUNCTIONS ------------------------------------------------------------ + +BOOL binn_save_header(binn *item); + +// create a new binn allocating memory for the structure +binn *binn_new(int type, int size, void *buffer); +binn *binn_list(); +binn *binn_map(); +binn *binn_object(); + +// create a new binn storing the structure on the stack +BOOL binn_create(binn *item, int type, int size, void *buffer); +BOOL binn_create_list(binn *list); +BOOL binn_create_map(binn *map); +BOOL binn_create_object(binn *object); + +// create a new binn as a copy from another +binn *binn_copy(void *old); + +BOOL binn_list_add_new(binn *list, binn *value); +BOOL binn_map_set_new(binn *map, int id, binn *value); +BOOL binn_object_set_new(binn *obj, const char *key, binn *value); +BOOL binn_object_set_new2(binn *obj, const char *key, int keylen, binn *value); + +// extended interface +BOOL binn_list_add(binn *list, int type, void *pvalue, int size); +BOOL binn_map_set(binn *map, int id, int type, void *pvalue, int size); +BOOL binn_object_set(binn *obj, const char *key, int type, void *pvalue, int size); +BOOL binn_object_set2(binn *obj, const char *key, int keylen, int type, void *pvalue, int size); + +// release memory +void binn_free(binn *item); + +// free the binn structure but keeps the binn buffer allocated, returning a pointer to it. +// use the free function to release the buffer later +void *binn_release(binn *item); + +// --- CREATING VALUES --------------------------------------------------- + +binn *binn_value(int type, void *pvalue, int size, binn_mem_free freefn); + +ALWAYS_INLINE void binn_init_item(binn *item) { + memset(item, 0, sizeof(binn)); + item->header = BINN_MAGIC; +} + +ALWAYS_INLINE binn *binn_int8(signed char value) { + return binn_value(BINN_INT8, &value, 0, NULL); +} + +ALWAYS_INLINE binn *binn_int16(short value) { + return binn_value(BINN_INT16, &value, 0, NULL); +} + +ALWAYS_INLINE binn *binn_int32(int value) { + return binn_value(BINN_INT32, &value, 0, NULL); +} + +ALWAYS_INLINE binn *binn_int64(int64 value) { + return binn_value(BINN_INT64, &value, 0, NULL); +} + +ALWAYS_INLINE binn *binn_uint8(unsigned char value) { + return binn_value(BINN_UINT8, &value, 0, NULL); +} + +ALWAYS_INLINE binn *binn_uint16(unsigned short value) { + return binn_value(BINN_UINT16, &value, 0, NULL); +} + +ALWAYS_INLINE binn *binn_uint32(unsigned int value) { + return binn_value(BINN_UINT32, &value, 0, NULL); +} + +ALWAYS_INLINE binn *binn_uint64(uint64 value) { + return binn_value(BINN_UINT64, &value, 0, NULL); +} + +ALWAYS_INLINE binn *binn_float(float value) { + return binn_value(BINN_FLOAT, &value, 0, NULL); +} + +ALWAYS_INLINE binn *binn_double(double value) { + return binn_value(BINN_DOUBLE, &value, 0, NULL); +} + +ALWAYS_INLINE binn *binn_bool(BOOL value) { + return binn_value(BINN_BOOL, &value, 0, NULL); +} + +ALWAYS_INLINE binn *binn_null() { + return binn_value(BINN_NULL, NULL, 0, NULL); +} + +ALWAYS_INLINE binn *binn_string(const char *str, binn_mem_free freefn) { + return binn_value(BINN_STRING, (void*) str, 0, freefn); +} + +ALWAYS_INLINE binn *binn_blob(void *ptr, int size, binn_mem_free freefn) { + return binn_value(BINN_BLOB, ptr, size, freefn); +} + +// --- READ FUNCTIONS ------------------------------------------------------------- + +// these functions accept pointer to the binn structure and pointer to the binn buffer +void *binn_ptr(void *ptr); +int binn_size(void *ptr); +int binn_buf_size(const void *ptr); +int binn_type(void *ptr); +int binn_buf_type(const void *pbuf); +int binn_count(void *ptr); +int binn_buf_count(const void *pbuf); +BOOL binn_is_valid_header(const void *pbuf, int *ptype, int *pcount, int *psize, int *pheadersize); + +BOOL binn_is_valid(void *ptr, int *ptype, int *pcount, int *psize); + +/* the function returns the values (type, count and size) and they don't need to be + initialized. these values are read from the buffer. example: + + int type, count, size; + result = binn_is_valid(ptr, &type, &count, &size); + */ +BOOL binn_is_valid_ex(void *ptr, int *ptype, int *pcount, int *psize); + +/* if some value is informed (type, count or size) then the function will check if + the value returned from the serialized data matches the informed value. otherwise + the values must be initialized to zero. example: + + int type=0, count=0, size = known_size; + result = binn_is_valid_ex(ptr, &type, &count, &size); + */ + +BOOL binn_is_struct(void *ptr); + +// Loading a binn buffer into a binn value - this is optional + +BOOL binn_load(void *data, binn *item); // on stack +binn *binn_open(void *data); // allocated + +// easiest interface to use, but don't check if the value is there + +signed char binn_list_int8(void *list, int pos); +short binn_list_int16(void *list, int pos); +int binn_list_int32(void *list, int pos); +int64 binn_list_int64(void *list, int pos); +unsigned char binn_list_uint8(void *list, int pos); +unsigned short binn_list_uint16(void *list, int pos); +unsigned int binn_list_uint32(void *list, int pos); +uint64 binn_list_uint64(void *list, int pos); +float binn_list_float(void *list, int pos); +double binn_list_double(void *list, int pos); +BOOL binn_list_bool(void *list, int pos); +BOOL binn_list_null(void *list, int pos); +char *binn_list_str(void *list, int pos); +void *binn_list_blob(void *list, int pos, int *psize); +void *binn_list_list(void *list, int pos); +void *binn_list_map(void *list, int pos); +void *binn_list_object(void *list, int pos); + +signed char binn_map_int8(void *map, int id); +short binn_map_int16(void *map, int id); +int binn_map_int32(void *map, int id); +int64 binn_map_int64(void *map, int id); +unsigned char binn_map_uint8(void *map, int id); +unsigned short binn_map_uint16(void *map, int id); +unsigned int binn_map_uint32(void *map, int id); +uint64 binn_map_uint64(void *map, int id); +float binn_map_float(void *map, int id); +double binn_map_double(void *map, int id); +BOOL binn_map_bool(void *map, int id); +BOOL binn_map_null(void *map, int id); +char *binn_map_str(void *map, int id); +void *binn_map_blob(void *map, int id, int *psize); +void *binn_map_list(void *map, int id); +void *binn_map_map(void *map, int id); +void *binn_map_object(void *map, int id); + +signed char binn_object_int8(void *obj, const char *key); +short binn_object_int16(void *obj, const char *key); +int binn_object_int32(void *obj, const char *key); +int64 binn_object_int64(void *obj, const char *key); +unsigned char binn_object_uint8(void *obj, const char *key); +unsigned short binn_object_uint16(void *obj, const char *key); +unsigned int binn_object_uint32(void *obj, const char *key); +uint64 binn_object_uint64(void *obj, const char *key); +float binn_object_float(void *obj, const char *key); +double binn_object_double(void *obj, const char *key); +BOOL binn_object_bool(void *obj, const char *key); +BOOL binn_object_null(void *obj, const char *key); +char *binn_object_str(void *obj, const char *key); +void *binn_object_blob(void *obj, const char *key, int *psize); +void *binn_object_list(void *obj, const char *key); +void *binn_object_map(void *obj, const char *key); +void *binn_object_object(void *obj, const char *key); + + +// return a pointer to an allocated binn structure - must be released with the free() function or equivalent set in +// binn_set_alloc_functions() +binn *binn_list_value(void *list, int pos); +binn *binn_map_value(void *map, int id); +binn *binn_object_value(void *obj, const char *key); + +// read the value to a binn structure on the stack +BOOL binn_list_get_value(void *list, int pos, binn *value); +BOOL binn_map_get_value(void *map, int id, binn *value); +BOOL binn_object_get_value(void *obj, const char *key, binn *value); + +// single interface - these functions check the data type +BOOL binn_list_get(void *list, int pos, int type, void *pvalue, int *psize); +BOOL binn_map_get(void *map, int id, int type, void *pvalue, int *psize); +BOOL binn_object_get(void *obj, const char *key, int type, void *pvalue, int *psize); + +// these 3 functions return a pointer to the value and the data type +// they are thread-safe on big-endian devices +// on little-endian devices they are thread-safe only to return pointers to list, map, object, blob and strings +// the returned pointer to 16, 32 and 64 bits values must be used only by single-threaded applications +void *binn_list_read(void *list, int pos, int *ptype, int *psize); +void *binn_map_read(void *map, int id, int *ptype, int *psize); +void *binn_object_read(void *obj, const char *key, int *ptype, int *psize); + +// READ PAIR FUNCTIONS + +// these functions use base 1 in the 'pos' argument + +// on stack +BOOL binn_map_get_pair(void *map, int pos, int *pid, binn *value); +BOOL binn_object_get_pair( + void *obj, int pos, char *pkey, + binn *value); // must free the memory returned in the pkey + +// allocated +binn *binn_map_pair(void *map, int pos, int *pid); +binn *binn_object_pair(void *obj, int pos, char *pkey); // must free the memory returned in the pkey + +// these 2 functions return a pointer to the value and the data type +// they are thread-safe on big-endian devices +// on little-endian devices they are thread-safe only to return pointers to list, map, object, blob and strings +// the returned pointer to 16, 32 and 64 bits values must be used only by single-threaded applications +void *binn_map_read_pair(void *ptr, int pos, int *pid, int *ptype, int *psize); +void *binn_object_read_pair(void *ptr, int pos, char *pkey, int *ptype, int *psize); + +// SEQUENTIAL READ FUNCTIONS + +typedef struct binn_iter_struct { + unsigned char *pnext; + unsigned char *plimit; + int type; + int count; + int current; +} binn_iter; + +BOOL binn_iter_init(binn_iter *iter, void *pbuf, int type); + +// allocated +binn *binn_list_next_value(binn_iter *iter); +binn *binn_map_next_value(binn_iter *iter, int *pid); +binn *binn_object_next_value(binn_iter *iter, char *pkey); // the key must be declared as: char key[256]; + +// on stack +BOOL binn_list_next(binn_iter *iter, binn *value); +BOOL binn_map_next(binn_iter *iter, int *pid, binn *value); +BOOL binn_object_next( + binn_iter *iter, char *pkey, + binn *value); // the key must be declared as: char key[256]; +BOOL binn_object_next2(binn_iter *iter, char **pkey, int *klen, binn *value); + +// these 3 functions return a pointer to the value and the data type +// they are thread-safe on big-endian devices +// on little-endian devices they are thread-safe only to return pointers to list, map, object, blob and strings +// the returned pointer to 16, 32 and 64 bits values must be used only by single-threaded applications +void *binn_list_read_next(binn_iter *iter, int *ptype, int *psize); +void *binn_map_read_next(binn_iter *iter, int *pid, int *ptype, int *psize); +void *binn_object_read_next( + binn_iter *iter, char *pkey, int *ptype, + int *psize); // the key must be declared as: char key[256]; + +// --- MACROS ------------------------------------------------------------ + +#define binn_is_writable(item) (item)->writable; + +// set values on stack allocated binn structures + +#define binn_set_null(item) do { (item)->type = BINN_NULL; } while (0) + +#define binn_set_bool(item, value) do { (item)->type = BINN_BOOL; (item)->vbool = value; (item)->ptr = &((item)->vbool); \ +} while (0) + +#define binn_set_int(item, value) do { (item)->type = BINN_INT32; (item)->vint32 = value; \ + (item)->ptr = &((item)->vint32); } while (0) +#define binn_set_int64(item, value) do { (item)->type = BINN_INT64; (item)->vint64 = value; \ + (item)->ptr = &((item)->vint64); } while (0) + +#define binn_set_uint(item, value) do { (item)->type = BINN_UINT32; (item)->vuint32 = value; \ + (item)->ptr = &((item)->vuint32); } while (0) +#define binn_set_uint64(item, value) do { (item)->type = BINN_UINT64; (item)->vuint64 = value; \ + (item)->ptr = &((item)->vuint64); } while (0) + +#define binn_set_float(item, value) do { (item)->type = BINN_FLOAT; (item)->vfloat = value; \ + (item)->ptr = &((item)->vfloat); } while (0) +#define binn_set_double(item, value) do { (item)->type = BINN_DOUBLE; (item)->vdouble = value; \ + (item)->ptr = &((item)->vdouble); } while (0) + +//#define binn_set_string(item,str,pfree) do { (item)->type = BINN_STRING; (item)->ptr = str; (item)->freefn = pfree; +// } while (0) +//#define binn_set_blob(item,ptr,size,pfree) do { (item)->type = BINN_BLOB; (item)->ptr = ptr; (item)->freefn = pfree; +// (item)->size = size; } while (0) +BOOL binn_set_string(binn *item, char *str, binn_mem_free pfree); +BOOL binn_set_blob(binn *item, void *ptr, int size, binn_mem_free pfree); + +//#define binn_double(value) { (item)->type = BINN_DOUBLE; (item)->vdouble = value; (item)->ptr = +// &((item)->vdouble) } + +// FOREACH MACROS +// must use these declarations in the function that will use them: +// binn_iter iter; +// char key[256]; // only for the object +// int id; // only for the map +// binn value; + +#define binn_object_foreach(object, key, value) \ + binn_iter_init(&iter, object, BINN_OBJECT); \ + while (binn_object_next(&iter, key, &value)) + +#define binn_map_foreach(map, id, value) \ + binn_iter_init(&iter, map, BINN_MAP); \ + while (binn_map_next(&iter, &id, &value)) + +#define binn_list_foreach(list, value) \ + binn_iter_init(&iter, list, BINN_LIST); \ + while (binn_list_next(&iter, &value)) + +/*************************************************************************************/ +/*** SET FUNCTIONS *******************************************************************/ +/*************************************************************************************/ + +ALWAYS_INLINE BOOL binn_list_add_int8(binn *list, signed char value) { + return binn_list_add(list, BINN_INT8, &value, 0); +} + +ALWAYS_INLINE BOOL binn_list_add_int16(binn *list, short value) { + return binn_list_add(list, BINN_INT16, &value, 0); +} + +ALWAYS_INLINE BOOL binn_list_add_int32(binn *list, int value) { + return binn_list_add(list, BINN_INT32, &value, 0); +} + +ALWAYS_INLINE BOOL binn_list_add_int64(binn *list, int64 value) { + return binn_list_add(list, BINN_INT64, &value, 0); +} + +ALWAYS_INLINE BOOL binn_list_add_uint8(binn *list, unsigned char value) { + return binn_list_add(list, BINN_UINT8, &value, 0); +} + +ALWAYS_INLINE BOOL binn_list_add_uint16(binn *list, unsigned short value) { + return binn_list_add(list, BINN_UINT16, &value, 0); +} + +ALWAYS_INLINE BOOL binn_list_add_uint32(binn *list, unsigned int value) { + return binn_list_add(list, BINN_UINT32, &value, 0); +} + +ALWAYS_INLINE BOOL binn_list_add_uint64(binn *list, uint64 value) { + return binn_list_add(list, BINN_UINT64, &value, 0); +} + +ALWAYS_INLINE BOOL binn_list_add_float(binn *list, float value) { + return binn_list_add(list, BINN_FLOAT32, &value, 0); +} + +ALWAYS_INLINE BOOL binn_list_add_double(binn *list, double value) { + return binn_list_add(list, BINN_FLOAT64, &value, 0); +} + +ALWAYS_INLINE BOOL binn_list_add_bool(binn *list, BOOL value) { + return binn_list_add(list, BINN_BOOL, &value, 0); +} + +ALWAYS_INLINE BOOL binn_list_add_null(binn *list) { + return binn_list_add(list, BINN_NULL, NULL, 0); +} + +ALWAYS_INLINE BOOL binn_list_add_str(binn *list, char *str) { + return binn_list_add(list, BINN_STRING, str, 0); +} + +ALWAYS_INLINE BOOL binn_list_add_const_str(binn *list, const char *str) { + return binn_list_add(list, BINN_STRING, (char*) str, 0); +} + +ALWAYS_INLINE BOOL binn_list_add_blob(binn *list, void *ptr, int size) { + return binn_list_add(list, BINN_BLOB, ptr, size); +} + +ALWAYS_INLINE BOOL binn_list_add_list(binn *list, void *list2) { + return binn_list_add(list, BINN_LIST, binn_ptr(list2), binn_size(list2)); +} + +ALWAYS_INLINE BOOL binn_list_add_map(binn *list, void *map) { + return binn_list_add(list, BINN_MAP, binn_ptr(map), binn_size(map)); +} + +ALWAYS_INLINE BOOL binn_list_add_object(binn *list, void *obj) { + return binn_list_add(list, BINN_OBJECT, binn_ptr(obj), binn_size(obj)); +} + +ALWAYS_INLINE BOOL binn_list_add_value(binn *list, binn *value) { + return binn_list_add(list, value->type, binn_ptr(value), binn_size(value)); +} + +/*************************************************************************************/ + +ALWAYS_INLINE BOOL binn_map_set_int8(binn *map, int id, signed char value) { + return binn_map_set(map, id, BINN_INT8, &value, 0); +} + +ALWAYS_INLINE BOOL binn_map_set_int16(binn *map, int id, short value) { + return binn_map_set(map, id, BINN_INT16, &value, 0); +} + +ALWAYS_INLINE BOOL binn_map_set_int32(binn *map, int id, int value) { + return binn_map_set(map, id, BINN_INT32, &value, 0); +} + +ALWAYS_INLINE BOOL binn_map_set_int64(binn *map, int id, int64 value) { + return binn_map_set(map, id, BINN_INT64, &value, 0); +} + +ALWAYS_INLINE BOOL binn_map_set_uint8(binn *map, int id, unsigned char value) { + return binn_map_set(map, id, BINN_UINT8, &value, 0); +} + +ALWAYS_INLINE BOOL binn_map_set_uint16(binn *map, int id, unsigned short value) { + return binn_map_set(map, id, BINN_UINT16, &value, 0); +} + +ALWAYS_INLINE BOOL binn_map_set_uint32(binn *map, int id, unsigned int value) { + return binn_map_set(map, id, BINN_UINT32, &value, 0); +} + +ALWAYS_INLINE BOOL binn_map_set_uint64(binn *map, int id, uint64 value) { + return binn_map_set(map, id, BINN_UINT64, &value, 0); +} + +ALWAYS_INLINE BOOL binn_map_set_float(binn *map, int id, float value) { + return binn_map_set(map, id, BINN_FLOAT32, &value, 0); +} + +ALWAYS_INLINE BOOL binn_map_set_double(binn *map, int id, double value) { + return binn_map_set(map, id, BINN_FLOAT64, &value, 0); +} + +ALWAYS_INLINE BOOL binn_map_set_bool(binn *map, int id, BOOL value) { + return binn_map_set(map, id, BINN_BOOL, &value, 0); +} + +ALWAYS_INLINE BOOL binn_map_set_null(binn *map, int id) { + return binn_map_set(map, id, BINN_NULL, NULL, 0); +} + +ALWAYS_INLINE BOOL binn_map_set_str(binn *map, int id, char *str) { + return binn_map_set(map, id, BINN_STRING, str, 0); +} + +ALWAYS_INLINE BOOL binn_map_set_blob(binn *map, int id, void *ptr, int size) { + return binn_map_set(map, id, BINN_BLOB, ptr, size); +} + +ALWAYS_INLINE BOOL binn_map_set_list(binn *map, int id, void *list) { + return binn_map_set(map, id, BINN_LIST, binn_ptr(list), binn_size(list)); +} + +ALWAYS_INLINE BOOL binn_map_set_map(binn *map, int id, void *map2) { + return binn_map_set(map, id, BINN_MAP, binn_ptr(map2), binn_size(map2)); +} + +ALWAYS_INLINE BOOL binn_map_set_object(binn *map, int id, void *obj) { + return binn_map_set(map, id, BINN_OBJECT, binn_ptr(obj), binn_size(obj)); +} + +ALWAYS_INLINE BOOL binn_map_set_value(binn *map, int id, binn *value) { + return binn_map_set(map, id, value->type, binn_ptr(value), binn_size(value)); +} + +/*************************************************************************************/ + +ALWAYS_INLINE BOOL binn_object_set_int8(binn *obj, const char *key, signed char value) { + return binn_object_set(obj, key, BINN_INT8, &value, 0); +} + +ALWAYS_INLINE BOOL binn_object_set_int16(binn *obj, const char *key, short value) { + return binn_object_set(obj, key, BINN_INT16, &value, 0); +} + +ALWAYS_INLINE BOOL binn_object_set_int32(binn *obj, const char *key, int value) { + return binn_object_set(obj, key, BINN_INT32, &value, 0); +} + +ALWAYS_INLINE BOOL binn_object_set_int64(binn *obj, const char *key, int64 value) { + return binn_object_set(obj, key, BINN_INT64, &value, 0); +} + +ALWAYS_INLINE BOOL binn_object_set_uint8(binn *obj, const char *key, unsigned char value) { + return binn_object_set(obj, key, BINN_UINT8, &value, 0); +} + +ALWAYS_INLINE BOOL binn_object_set_uint16(binn *obj, const char *key, unsigned short value) { + return binn_object_set(obj, key, BINN_UINT16, &value, 0); +} + +ALWAYS_INLINE BOOL binn_object_set_uint32(binn *obj, const char *key, unsigned int value) { + return binn_object_set(obj, key, BINN_UINT32, &value, 0); +} + +ALWAYS_INLINE BOOL binn_object_set_uint64(binn *obj, const char *key, uint64 value) { + return binn_object_set(obj, key, BINN_UINT64, &value, 0); +} + +ALWAYS_INLINE BOOL binn_object_set_float(binn *obj, const char *key, float value) { + return binn_object_set(obj, key, BINN_FLOAT32, &value, 0); +} + +ALWAYS_INLINE BOOL binn_object_set_double(binn *obj, const char *key, double value) { + return binn_object_set(obj, key, BINN_FLOAT64, &value, 0); +} + +ALWAYS_INLINE BOOL binn_object_set_bool(binn *obj, const char *key, BOOL value) { + return binn_object_set(obj, key, BINN_BOOL, &value, 0); +} + +ALWAYS_INLINE BOOL binn_object_set_null(binn *obj, const char *key) { + return binn_object_set(obj, key, BINN_NULL, NULL, 0); +} + +ALWAYS_INLINE BOOL binn_object_set_str(binn *obj, const char *key, const char *str) { + return binn_object_set(obj, key, BINN_STRING, (char*) str, 0); // todo +} + +ALWAYS_INLINE BOOL binn_object_set_blob(binn *obj, const char *key, void *ptr, int size) { + return binn_object_set(obj, key, BINN_BLOB, ptr, size); +} + +ALWAYS_INLINE BOOL binn_object_set_list(binn *obj, const char *key, void *list) { + return binn_object_set(obj, key, BINN_LIST, binn_ptr(list), binn_size(list)); +} + +ALWAYS_INLINE BOOL binn_object_set_map(binn *obj, const char *key, void *map) { + return binn_object_set(obj, key, BINN_MAP, binn_ptr(map), binn_size(map)); +} + +ALWAYS_INLINE BOOL binn_object_set_object(binn *obj, const char *key, void *obj2) { + return binn_object_set(obj, key, BINN_OBJECT, binn_ptr(obj2), binn_size(obj2)); +} + +ALWAYS_INLINE BOOL binn_object_set_value(binn *obj, const char *key, binn *value) { + return binn_object_set(obj, key, value->type, binn_ptr(value), binn_size(value)); +} + +ALWAYS_INLINE BOOL binn_object_set_value2(binn *obj, const char *key, int keylen, binn *value) { + return binn_object_set2(obj, key, keylen, value->type, binn_ptr(value), binn_size(value)); +} + +/*************************************************************************************/ +/*** GET FUNCTIONS *******************************************************************/ +/*************************************************************************************/ + +ALWAYS_INLINE BOOL binn_list_get_int8(void *list, int pos, signed char *pvalue) { + return binn_list_get(list, pos, BINN_INT8, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_list_get_int16(void *list, int pos, short *pvalue) { + return binn_list_get(list, pos, BINN_INT16, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_list_get_int32(void *list, int pos, int *pvalue) { + return binn_list_get(list, pos, BINN_INT32, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_list_get_int64(void *list, int pos, int64 *pvalue) { + return binn_list_get(list, pos, BINN_INT64, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_list_get_uint8(void *list, int pos, unsigned char *pvalue) { + return binn_list_get(list, pos, BINN_UINT8, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_list_get_uint16(void *list, int pos, unsigned short *pvalue) { + return binn_list_get(list, pos, BINN_UINT16, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_list_get_uint32(void *list, int pos, unsigned int *pvalue) { + return binn_list_get(list, pos, BINN_UINT32, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_list_get_uint64(void *list, int pos, uint64 *pvalue) { + return binn_list_get(list, pos, BINN_UINT64, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_list_get_float(void *list, int pos, float *pvalue) { + return binn_list_get(list, pos, BINN_FLOAT32, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_list_get_double(void *list, int pos, double *pvalue) { + return binn_list_get(list, pos, BINN_FLOAT64, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_list_get_bool(void *list, int pos, BOOL *pvalue) { + return binn_list_get(list, pos, BINN_BOOL, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_list_get_str(void *list, int pos, char **pvalue) { + return binn_list_get(list, pos, BINN_STRING, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_list_get_blob(void *list, int pos, void **pvalue, int *psize) { + return binn_list_get(list, pos, BINN_BLOB, pvalue, psize); +} + +ALWAYS_INLINE BOOL binn_list_get_list(void *list, int pos, void **pvalue) { + return binn_list_get(list, pos, BINN_LIST, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_list_get_map(void *list, int pos, void **pvalue) { + return binn_list_get(list, pos, BINN_MAP, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_list_get_object(void *list, int pos, void **pvalue) { + return binn_list_get(list, pos, BINN_OBJECT, pvalue, NULL); +} + +/***************************************************************************/ + +ALWAYS_INLINE BOOL binn_map_get_int8(void *map, int id, signed char *pvalue) { + return binn_map_get(map, id, BINN_INT8, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_map_get_int16(void *map, int id, short *pvalue) { + return binn_map_get(map, id, BINN_INT16, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_map_get_int32(void *map, int id, int *pvalue) { + return binn_map_get(map, id, BINN_INT32, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_map_get_int64(void *map, int id, int64 *pvalue) { + return binn_map_get(map, id, BINN_INT64, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_map_get_uint8(void *map, int id, unsigned char *pvalue) { + return binn_map_get(map, id, BINN_UINT8, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_map_get_uint16(void *map, int id, unsigned short *pvalue) { + return binn_map_get(map, id, BINN_UINT16, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_map_get_uint32(void *map, int id, unsigned int *pvalue) { + return binn_map_get(map, id, BINN_UINT32, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_map_get_uint64(void *map, int id, uint64 *pvalue) { + return binn_map_get(map, id, BINN_UINT64, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_map_get_float(void *map, int id, float *pvalue) { + return binn_map_get(map, id, BINN_FLOAT32, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_map_get_double(void *map, int id, double *pvalue) { + return binn_map_get(map, id, BINN_FLOAT64, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_map_get_bool(void *map, int id, BOOL *pvalue) { + return binn_map_get(map, id, BINN_BOOL, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_map_get_str(void *map, int id, char **pvalue) { + return binn_map_get(map, id, BINN_STRING, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_map_get_blob(void *map, int id, void **pvalue, int *psize) { + return binn_map_get(map, id, BINN_BLOB, pvalue, psize); +} + +ALWAYS_INLINE BOOL binn_map_get_list(void *map, int id, void **pvalue) { + return binn_map_get(map, id, BINN_LIST, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_map_get_map(void *map, int id, void **pvalue) { + return binn_map_get(map, id, BINN_MAP, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_map_get_object(void *map, int id, void **pvalue) { + return binn_map_get(map, id, BINN_OBJECT, pvalue, NULL); +} + +/***************************************************************************/ + +// usage: +// if (binn_object_get_int32(obj, "key", &value) == FALSE) xxx; + +ALWAYS_INLINE BOOL binn_object_get_int8(void *obj, const char *key, signed char *pvalue) { + return binn_object_get(obj, key, BINN_INT8, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_object_get_int16(void *obj, const char *key, short *pvalue) { + return binn_object_get(obj, key, BINN_INT16, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_object_get_int32(void *obj, const char *key, int *pvalue) { + return binn_object_get(obj, key, BINN_INT32, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_object_get_int64(void *obj, const char *key, int64 *pvalue) { + return binn_object_get(obj, key, BINN_INT64, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_object_get_uint8(void *obj, const char *key, unsigned char *pvalue) { + return binn_object_get(obj, key, BINN_UINT8, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_object_get_uint16(void *obj, const char *key, unsigned short *pvalue) { + return binn_object_get(obj, key, BINN_UINT16, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_object_get_uint32(void *obj, const char *key, unsigned int *pvalue) { + return binn_object_get(obj, key, BINN_UINT32, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_object_get_uint64(void *obj, const char *key, uint64 *pvalue) { + return binn_object_get(obj, key, BINN_UINT64, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_object_get_float(void *obj, const char *key, float *pvalue) { + return binn_object_get(obj, key, BINN_FLOAT32, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_object_get_double(void *obj, const char *key, double *pvalue) { + return binn_object_get(obj, key, BINN_FLOAT64, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_object_get_bool(void *obj, const char *key, BOOL *pvalue) { + return binn_object_get(obj, key, BINN_BOOL, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_object_get_str(void *obj, const char *key, char **pvalue) { + return binn_object_get(obj, key, BINN_STRING, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_object_get_blob(void *obj, const char *key, void **pvalue, int *psize) { + return binn_object_get(obj, key, BINN_BLOB, pvalue, psize); +} + +ALWAYS_INLINE BOOL binn_object_get_list(void *obj, const char *key, void **pvalue) { + return binn_object_get(obj, key, BINN_LIST, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_object_get_map(void *obj, const char *key, void **pvalue) { + return binn_object_get(obj, key, BINN_MAP, pvalue, NULL); +} + +ALWAYS_INLINE BOOL binn_object_get_object(void *obj, const char *key, void **pvalue) { + return binn_object_get(obj, key, BINN_OBJECT, pvalue, NULL); +} + +/***************************************************************************/ + +BOOL binn_get_int32(binn *value, int *pint); +BOOL binn_get_int64(binn *value, int64 *pint); +BOOL binn_get_double(binn *value, double *pfloat); +BOOL binn_get_bool(binn *value, BOOL *pbool); +char *binn_get_str(binn *value); + +// boolean string values: +// 1, true, yes, on +// 0, false, no, off + +// boolean number values: +// !=0 [true] +// ==0 [false] + + +#ifdef __cplusplus +} +#endif + +#endif //BINN_H diff --git a/src/jbl/jbl.c b/src/jbl/jbl.c new file mode 100644 index 0000000..b28ca9b --- /dev/null +++ b/src/jbl/jbl.c @@ -0,0 +1,2948 @@ +#include "jbl.h" +#include +#include +#include +#include "jbl_internal.h" +#include "utf8proc.h" +#include "convert.h" + +#define _STRX(x) #x +#define _STR(x) _STRX(x) + +IW_INLINE int _jbl_printf_estimate_size(const char *format, va_list ap) { + char buf[1]; + return vsnprintf(buf, sizeof(buf), format, ap) + 1; +} + +IW_INLINE void _jbn_remove_item(JBL_NODE parent, JBL_NODE child); +static void _jbn_add_item(JBL_NODE parent, JBL_NODE node); + +iwrc jbl_create_empty_object(JBL *jblp) { + *jblp = calloc(1, sizeof(**jblp)); + if (!*jblp) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + binn_create(&(*jblp)->bn, BINN_OBJECT, 0, 0); + return 0; +} + +iwrc jbl_create_empty_array(JBL *jblp) { + *jblp = calloc(1, sizeof(**jblp)); + if (!*jblp) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + binn_create(&(*jblp)->bn, BINN_LIST, 0, 0); + return 0; +} + +void jbl_set_user_data(JBL jbl, void *user_data, void (*user_data_free_fn)(void*)) { + binn_set_user_data(&jbl->bn, user_data, user_data_free_fn); +} + +void *jbl_get_user_data(JBL jbl) { + return jbl->bn.user_data; +} + +iwrc jbl_set_int64(JBL jbl, const char *key, int64_t v) { + jbl_type_t t = jbl_type(jbl); + if (((t != JBV_OBJECT) && (t != JBV_ARRAY)) || !jbl->bn.writable) { + return JBL_ERROR_CREATION; + } + binn *bv = &jbl->bn; + if (key) { + if (t == JBV_OBJECT) { + if (!binn_object_set_int64(bv, key, v)) { + return JBL_ERROR_CREATION; + } + } else { + return JBL_ERROR_CREATION; + } + return 0; + } else if (t == JBV_ARRAY) { + if (!binn_list_add_int64(bv, v)) { + return JBL_ERROR_CREATION; + } + return 0; + } + return JBL_ERROR_INVALID; +} + +iwrc jbl_set_f64(JBL jbl, const char *key, double v) { + jbl_type_t t = jbl_type(jbl); + if (((t != JBV_OBJECT) && (t != JBV_ARRAY)) || !jbl->bn.writable) { + return JBL_ERROR_CREATION; + } + binn *bv = &jbl->bn; + if (key) { + if (t == JBV_OBJECT) { + if (!binn_object_set_double(bv, key, v)) { + return JBL_ERROR_CREATION; + } + } else { + return JBL_ERROR_CREATION; + } + return 0; + } else if (t == JBV_ARRAY) { + if (!binn_list_add_double(bv, v)) { + return JBL_ERROR_CREATION; + } + return 0; + } + return JBL_ERROR_INVALID; +} + +iwrc jbl_set_string(JBL jbl, const char *key, const char *v) { + jbl_type_t t = jbl_type(jbl); + if (((t != JBV_OBJECT) && (t != JBV_ARRAY)) || !jbl->bn.writable) { + return JBL_ERROR_CREATION; + } + binn *bv = &jbl->bn; + if (key) { + if (t == JBV_OBJECT) { + if (!binn_object_set_str(bv, key, v)) { + return JBL_ERROR_CREATION; + } + } else { + return JBL_ERROR_CREATION; + } + return 0; + } else if (t == JBV_ARRAY) { + if (!binn_list_add_const_str(bv, v)) { + return JBL_ERROR_CREATION; + } + return 0; + } + return JBL_ERROR_INVALID; +} + +iwrc jbl_set_string_printf(JBL jbl, const char *key, const char *format, ...) { + iwrc rc = 0; + va_list ap; + + va_start(ap, format); + int size = _jbl_printf_estimate_size(format, ap); + va_end(ap); + + va_start(ap, format); + char *buf = malloc(size); + RCGA(buf, finish); + vsnprintf(buf, size, format, ap); + va_end(ap); + + rc = jbl_set_string(jbl, key, buf); +finish: + free(buf); + return rc; +} + +iwrc jbl_from_json_printf_va(JBL *jblp, const char *format, va_list va) { + iwrc rc = 0; + va_list cva; + + va_copy(cva, va); + int size = _jbl_printf_estimate_size(format, va); + char *buf = malloc(size); + RCGA(buf, finish); + vsnprintf(buf, size, format, cva); + va_end(cva); + + rc = jbl_from_json(jblp, buf); + +finish: + free(buf); + return rc; +} + +iwrc jbl_from_json_printf(JBL *jblp, const char *format, ...) { + va_list ap; + + va_start(ap, format); + iwrc rc = jbl_from_json_printf_va(jblp, format, ap); + va_end(ap); + return rc; +} + +iwrc jbn_from_json_printf_va(JBL_NODE *node, IWPOOL *pool, const char *format, va_list va) { + iwrc rc = 0; + va_list cva; + + va_copy(cva, va); + int size = _jbl_printf_estimate_size(format, va); + char *buf = malloc(size); + RCGA(buf, finish); + vsnprintf(buf, size, format, cva); + va_end(cva); + + rc = jbn_from_json(buf, node, pool); + +finish: + free(buf); + return rc; +} + +iwrc jbn_from_json_printf(JBL_NODE *node, IWPOOL *pool, const char *format, ...) { + va_list ap; + + va_start(ap, format); + iwrc rc = jbn_from_json_printf_va(node, pool, format, ap); + va_end(ap); + return rc; +} + +iwrc jbl_set_bool(JBL jbl, const char *key, bool v) { + jbl_type_t t = jbl_type(jbl); + if (((t != JBV_OBJECT) && (t != JBV_ARRAY)) || !jbl->bn.writable) { + return JBL_ERROR_CREATION; + } + binn *bv = &jbl->bn; + if (key) { + if (t == JBV_OBJECT) { + if (!binn_object_set_bool(bv, key, v)) { + return JBL_ERROR_CREATION; + } + } else { + return JBL_ERROR_CREATION; + } + return 0; + } else if (t == JBV_ARRAY) { + if (!binn_list_add_bool(bv, v)) { + return JBL_ERROR_CREATION; + } + return 0; + } + return JBL_ERROR_INVALID; +} + +iwrc jbl_set_null(JBL jbl, const char *key) { + jbl_type_t t = jbl_type(jbl); + if (((t != JBV_OBJECT) && (t != JBV_ARRAY)) || !jbl->bn.writable) { + return JBL_ERROR_CREATION; + } + binn *bv = &jbl->bn; + if (key) { + if (t == JBV_OBJECT) { + if (!binn_object_set_null(bv, key)) { + return JBL_ERROR_CREATION; + } + } else { + return JBL_ERROR_CREATION; + } + return 0; + } else if (t == JBV_ARRAY) { + if (!binn_list_add_null(bv)) { + return JBL_ERROR_CREATION; + } + return 0; + } + return JBL_ERROR_INVALID; +} + +iwrc jbl_set_empty_array(JBL jbl, const char *key) { + JBL v = 0; + iwrc rc = jbl_create_empty_array(&v); + RCGO(rc, finish); + rc = jbl_set_nested(jbl, key, v); +finish: + jbl_destroy(&v); + return rc; +} + +iwrc jbl_set_empty_object(JBL jbl, const char *key) { + JBL v = 0; + iwrc rc = jbl_create_empty_object(&v); + RCGO(rc, finish); + rc = jbl_set_nested(jbl, key, v); +finish: + jbl_destroy(&v); + return rc; +} + +iwrc jbl_set_nested(JBL jbl, const char *key, JBL v) { + jbl_type_t t = jbl_type(jbl); + if (((t != JBV_OBJECT) && (t != JBV_ARRAY)) || !jbl->bn.writable) { + return JBL_ERROR_CREATION; + } + binn *bv = &jbl->bn; + if (key) { + if (t == JBV_OBJECT) { + if (!binn_object_set_value(bv, key, &v->bn)) { + return JBL_ERROR_CREATION; + } + } else { + return JBL_ERROR_CREATION; + } + return 0; + } else if (t == JBV_ARRAY) { + if (!binn_list_add_value(bv, &v->bn)) { + return JBL_ERROR_CREATION; + } + return 0; + } + return JBL_ERROR_INVALID; +} + +iwrc jbl_from_buf_keep(JBL *jblp, void *buf, size_t bufsz, bool keep_on_destroy) { + int type, size = 0, count = 0; + if ((bufsz < MIN_BINN_SIZE) || !binn_is_valid_header(buf, &type, &count, &size, NULL)) { + return JBL_ERROR_INVALID_BUFFER; + } + if (size > bufsz) { + return JBL_ERROR_INVALID_BUFFER; + } + *jblp = calloc(1, sizeof(**jblp)); + if (!*jblp) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + JBL jbl = *jblp; + jbl->bn.header = BINN_MAGIC; + jbl->bn.type = type; + jbl->bn.ptr = buf; + jbl->bn.size = size; + jbl->bn.count = count; + jbl->bn.freefn = keep_on_destroy ? 0 : free; + return 0; +} + +iwrc jbl_clone(JBL src, JBL *targetp) { + *targetp = calloc(1, sizeof(**targetp)); + JBL t = *targetp; + if (!t) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + binn *bn = binn_copy(&src->bn); + if (!bn) { + return JBL_ERROR_CREATION; + } + t->node = 0; + bn->allocated = 0; + memcpy(&t->bn, bn, sizeof(*bn)); + free(bn); + return 0; +} + +IW_EXPORT iwrc jbl_object_copy_to(JBL src, JBL target) { + iwrc rc = 0; + // According to binn spec keys are not null terminated + // and key length is not more than 255 bytes + char *key, kbuf[256]; + int klen; + JBL holder = 0; + JBL_iterator it; + + if ((jbl_type(src) != JBV_OBJECT) || (jbl_type(target) != JBV_OBJECT)) { + return JBL_ERROR_NOT_AN_OBJECT; + } + RCC(rc, finish, jbl_create_iterator_holder(&holder)); + RCC(rc, finish, jbl_iterator_init(src, &it)); + while (jbl_iterator_next(&it, holder, &key, &klen)) { + memcpy(kbuf, key, klen); + kbuf[klen] = '\0'; + RCC(rc, finish, jbl_set_nested(target, kbuf, holder)); + } + +finish: + jbl_destroy(&holder); + return rc; +} + +iwrc jbl_clone_into_pool(JBL src, JBL *targetp, IWPOOL *pool) { + *targetp = 0; + if (src->bn.writable && src->bn.dirty) { + if (!binn_save_header(&src->bn)) { + return JBL_ERROR_INVALID; + } + } + JBL jbl = iwpool_alloc(sizeof(*jbl) + src->bn.size, pool); + if (!jbl) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + jbl->node = 0; + memcpy(&jbl->bn, &src->bn, sizeof(jbl->bn)); + jbl->bn.ptr = (char*) jbl + sizeof(*jbl); + memcpy(jbl->bn.ptr, src->bn.ptr, src->bn.size); + jbl->bn.freefn = 0; + *targetp = jbl; + return 0; +} + +iwrc jbl_from_buf_keep_onstack(JBL jbl, void *buf, size_t bufsz) { + int type, size = 0, count = 0; + if ((bufsz < MIN_BINN_SIZE) || !binn_is_valid_header(buf, &type, &count, &size, NULL)) { + return JBL_ERROR_INVALID_BUFFER; + } + if (size > bufsz) { + return JBL_ERROR_INVALID_BUFFER; + } + memset(jbl, 0, sizeof(*jbl)); + jbl->bn.header = BINN_MAGIC; + jbl->bn.type = type; + jbl->bn.ptr = buf; + jbl->bn.size = size; + jbl->bn.count = count; + return 0; +} + +iwrc jbl_from_buf_keep_onstack2(JBL jbl, void *buf) { + int type, size = 0, count = 0; + if (!binn_is_valid_header(buf, &type, &count, &size, NULL)) { + return JBL_ERROR_INVALID_BUFFER; + } + memset(jbl, 0, sizeof(*jbl)); + jbl->bn.header = BINN_MAGIC; + jbl->bn.type = type; + jbl->bn.ptr = buf; + jbl->bn.size = size; + jbl->bn.count = count; + return 0; +} + +void jbl_destroy(JBL *jblp) { + if (*jblp) { + JBL jbl = *jblp; + binn_free(&jbl->bn); + free(jbl); + *jblp = 0; + } +} + +iwrc jbl_create_iterator_holder(JBL *jblp) { + *jblp = calloc(1, sizeof(**jblp)); + if (!*jblp) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + return 0; +} + +iwrc jbl_iterator_init(JBL jbl, JBL_iterator *iter) { + int btype = jbl->bn.type; + if ((btype != BINN_OBJECT) && (btype != BINN_LIST) && (btype != BINN_MAP)) { + memset(iter, 0, sizeof(*iter)); + return 0; + } + binn_iter *biter = (binn_iter*) iter; + if (!binn_iter_init(biter, &jbl->bn, btype)) { + return JBL_ERROR_CREATION; + } + return 0; +} + +bool jbl_iterator_next(JBL_iterator *iter, JBL holder, char **pkey, int *klen) { + binn_iter *biter = (binn_iter*) iter; + if (pkey) { + *pkey = 0; + } + if (klen) { + *klen = 0; + } + if (!iter || (iter->type == 0)) { + return false; + } + if (iter->type == BINN_LIST) { + if (klen) { + *klen = iter->current; + } + return binn_list_next(biter, &holder->bn); + } else { + return binn_read_next_pair2(iter->type, biter, klen, pkey, &holder->bn); + } + return false; +} + +IW_INLINE jbl_type_t _jbl_binn_type(int btype) { + switch (btype) { + case BINN_NULL: + return JBV_NULL; + case BINN_STRING: + return JBV_STR; + case BINN_OBJECT: + case BINN_MAP: + return JBV_OBJECT; + case BINN_LIST: + return JBV_ARRAY; + case BINN_BOOL: + case BINN_TRUE: + case BINN_FALSE: + return JBV_BOOL; + case BINN_UINT8: + case BINN_UINT16: + case BINN_UINT32: + case BINN_UINT64: + case BINN_INT8: + case BINN_INT16: + case BINN_INT32: + case BINN_INT64: + return JBV_I64; + case BINN_FLOAT32: + case BINN_FLOAT64: + return JBV_F64; + default: + return JBV_NONE; + } +} + +jbl_type_t jbl_type(JBL jbl) { + if (jbl) { + return _jbl_binn_type(jbl->bn.type); + } + return JBV_NONE; +} + +size_t jbl_count(JBL jbl) { + return (size_t) jbl->bn.count; +} + +size_t jbl_size(JBL jbl) { + return (size_t) jbl->bn.size; +} + +size_t jbl_structure_size(void) { + return sizeof(struct _JBL); +} + +iwrc jbl_from_json(JBL *jblp, const char *jsonstr) { + *jblp = 0; + iwrc rc = 0; + IWPOOL *pool = iwpool_create(2 * strlen(jsonstr)); + if (!pool) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + JBL jbl; + JBL_NODE node; + rc = jbn_from_json(jsonstr, &node, pool); + RCGO(rc, finish); + if (node->type == JBV_OBJECT) { + rc = jbl_create_empty_object(&jbl); + RCGO(rc, finish); + } else if (node->type == JBV_ARRAY) { + rc = jbl_create_empty_array(&jbl); + RCGO(rc, finish); + } else { + // TODO: Review + rc = JBL_ERROR_CREATION; + goto finish; + } + rc = jbl_fill_from_node(jbl, node); + if (!rc) { + *jblp = jbl; + } + +finish: + iwpool_destroy(pool); + return rc; +} + +iwrc _jbl_write_double(double num, jbl_json_printer pt, void *op) { + size_t sz; + char buf[JBNUMBUF_SIZE]; + jbi_ftoa(num, buf, &sz); + return pt(buf, -1, 0, 0, op); +} + +iwrc _jbl_write_int(int64_t num, jbl_json_printer pt, void *op) { + char buf[JBNUMBUF_SIZE]; + int sz = iwitoa(num, buf, sizeof(buf)); + return pt(buf, sz, 0, 0, op); +} + +iwrc _jbl_write_string(const char *str, int len, jbl_json_printer pt, void *op, jbl_print_flags_t pf) { + iwrc rc = pt(0, 0, '"', 1, op); + RCRET(rc); + static const char *specials = "btnvfr"; + const uint8_t *p = (const uint8_t*) str; + +#define PT(data_, size_, ch_, count_) do { \ + rc = pt((const char*) (data_), size_, ch_, count_, op); \ + RCRET(rc); \ +} while (0) + + if (len < 0) { + len = (int) strlen(str); + } + for (size_t i = 0; i < len; i++) { + uint8_t ch = p[i]; + if ((ch == '"') || (ch == '\\')) { + PT(0, 0, '\\', 1); + PT(0, 0, ch, 1); + } else if ((ch >= '\b') && (ch <= '\r')) { + PT(0, 0, '\\', 1); + PT(0, 0, specials[ch - '\b'], 1); + } else if (isprint(ch)) { + PT(0, 0, ch, 1); + } else if (pf & JBL_PRINT_CODEPOINTS) { + char sbuf[7]; // escaped unicode seq + utf8proc_int32_t cp; + utf8proc_ssize_t sz = utf8proc_iterate(p + i, len - i, &cp); + if (sz < 0) { + return JBL_ERROR_PARSE_INVALID_UTF8; + } + if (cp > 0x0010000UL) { + uint32_t hs = 0xD800, ls = 0xDC00; // surrogates + cp -= 0x0010000UL; + hs |= ((cp >> 10) & 0x3FF); + ls |= (cp & 0x3FF); + snprintf(sbuf, 7, "\\u%04X", hs); + PT(sbuf, 6, 0, 0); + snprintf(sbuf, 7, "\\u%04X", ls); + PT(sbuf, 6, 0, 0); + } else { + snprintf(sbuf, 7, "\\u%04X", cp); + PT(sbuf, 6, 0, 0); + } + i += sz - 1; + } else { + PT(0, 0, ch, 1); + } + } + rc = pt(0, 0, '"', 1, op); + return rc; +#undef PT +} + +static iwrc _jbl_as_json(binn *bn, jbl_json_printer pt, void *op, int lvl, jbl_print_flags_t pf) { + iwrc rc = 0; + binn bv; + binn_iter iter; + int lv; + int64_t llv; + double dv; + char key[MAX_BIN_KEY_LEN + 1]; + bool pretty = pf & JBL_PRINT_PRETTY; + +#define PT(data_, size_, ch_, count_) do { \ + rc = pt(data_, size_, ch_, count_, op); \ + RCGO(rc, finish); \ +} while (0) + + switch (bn->type) { + + case BINN_LIST: + if (!binn_iter_init(&iter, bn, bn->type)) { + rc = JBL_ERROR_INVALID; + goto finish; + } + PT(0, 0, '[', 1); + if (bn->count && pretty) { + PT(0, 0, '\n', 1); + } + for (int i = 0; binn_list_next(&iter, &bv); ++i) { + if (pretty) { + PT(0, 0, ' ', lvl + 1); + } + rc = _jbl_as_json(&bv, pt, op, lvl + 1, pf); + RCGO(rc, finish); + if (i < bn->count - 1) { + PT(0, 0, ',', 1); + } + if (pretty) { + PT(0, 0, '\n', 1); + } + } + if (bn->count && pretty) { + PT(0, 0, ' ', lvl); + } + PT(0, 0, ']', 1); + break; + + case BINN_OBJECT: + case BINN_MAP: + if (!binn_iter_init(&iter, bn, bn->type)) { + rc = JBL_ERROR_INVALID; + goto finish; + } + PT(0, 0, '{', 1); + if (bn->count && pretty) { + PT(0, 0, '\n', 1); + } + if (bn->type == BINN_OBJECT) { + for (int i = 0; binn_object_next(&iter, key, &bv); ++i) { + if (pretty) { + PT(0, 0, ' ', lvl + 1); + } + rc = _jbl_write_string(key, -1, pt, op, pf); + RCGO(rc, finish); + if (pretty) { + PT(": ", -1, 0, 0); + } else { + PT(0, 0, ':', 1); + } + rc = _jbl_as_json(&bv, pt, op, lvl + 1, pf); + RCGO(rc, finish); + if (i < bn->count - 1) { + PT(0, 0, ',', 1); + } + if (pretty) { + PT(0, 0, '\n', 1); + } + } + } else { + for (int i = 0; binn_map_next(&iter, &lv, &bv); ++i) { + if (pretty) { + PT(0, 0, ' ', lvl + 1); + } + PT(0, 0, '"', 1); + rc = _jbl_write_int(lv, pt, op); + RCGO(rc, finish); + PT(0, 0, '"', 1); + if (pretty) { + PT(": ", -1, 0, 0); + } else { + PT(0, 0, ':', 1); + } + rc = _jbl_as_json(&bv, pt, op, lvl + 1, pf); + RCGO(rc, finish); + if (i < bn->count - 1) { + PT(0, 0, ',', 1); + } + if (pretty) { + PT(0, 0, '\n', 1); + } + } + } + if (bn->count && pretty) { + PT(0, 0, ' ', lvl); + } + PT(0, 0, '}', 1); + break; + + case BINN_STRING: + rc = _jbl_write_string(bn->ptr, -1, pt, op, pf); + break; + case BINN_UINT8: + llv = bn->vuint8; + goto loc_int; + case BINN_UINT16: + llv = bn->vuint16; + goto loc_int; + case BINN_UINT32: + llv = bn->vuint32; + goto loc_int; + case BINN_INT8: + llv = bn->vint8; // NOLINT(bugprone-signed-char-misuse) + goto loc_int; + case BINN_INT16: + llv = bn->vint16; + goto loc_int; + case BINN_INT32: + llv = bn->vint32; + goto loc_int; + case BINN_INT64: + llv = bn->vint64; + goto loc_int; + case BINN_UINT64: // overflow? + llv = (int64_t) bn->vuint64; +loc_int: + rc = _jbl_write_int(llv, pt, op); + break; + + case BINN_FLOAT32: + dv = bn->vfloat; + goto loc_float; + case BINN_FLOAT64: + dv = bn->vdouble; +loc_float: + rc = _jbl_write_double(dv, pt, op); + break; + + case BINN_TRUE: + PT("true", 4, 0, 0); + break; + case BINN_FALSE: + PT("false", 5, 0, 0); + break; + case BINN_BOOL: + PT(bn->vbool ? "true" : "false", -1, 0, 1); + break; + case BINN_NULL: + PT("null", 4, 0, 0); + break; + default: + iwlog_ecode_error3(IW_ERROR_ASSERTION); + rc = IW_ERROR_ASSERTION; + break; + } + +finish: + return rc; +#undef PT +} + +iwrc jbl_as_json(JBL jbl, jbl_json_printer pt, void *op, jbl_print_flags_t pf) { + if (!jbl || !pt) { + return IW_ERROR_INVALID_ARGS; + } + return _jbl_as_json(&jbl->bn, pt, op, 0, pf); +} + +iwrc jbl_fstream_json_printer(const char *data, int size, char ch, int count, void *op) { + FILE *file = op; + if (!file) { + return IW_ERROR_INVALID_ARGS; + } + if (!data) { + if (count) { + char cbuf[count]; // TODO: review overflow + memset(cbuf, ch, sizeof(cbuf)); + size_t wc = fwrite(cbuf, 1, count, file); + if (wc != sizeof(cbuf)) { + return iwrc_set_errno(IW_ERROR_IO_ERRNO, errno); + } + } + } else { + if (size < 0) { + size = (int) strlen(data); + } + if (!count) { + count = 1; + } + for (int i = 0; i < count; ++i) { + if (fprintf(file, "%.*s", size, data) < 0) { + return iwrc_set_errno(IW_ERROR_IO_ERRNO, errno); + } + } + } + return 0; +} + +iwrc jbl_xstr_json_printer(const char *data, int size, char ch, int count, void *op) { + IWXSTR *xstr = op; + if (!xstr) { + return IW_ERROR_INVALID_ARGS; + } + if (!data) { + if (count) { + for (int i = 0; i < count; ++i) { + iwrc rc = iwxstr_cat(xstr, &ch, 1); + RCRET(rc); + } + } + } else { + if (size < 0) { + size = (int) strlen(data); + } + if (!count) { + count = 1; + } + for (int i = 0; i < count; ++i) { + iwrc rc = iwxstr_cat(xstr, data, size); + RCRET(rc); + } + } + return 0; +} + +iwrc jbl_count_json_printer(const char *data, int size, char ch, int count, void *op) { + int *cnt = op; + if (!data) { + *cnt = *cnt + count; + } else { + if (size < 0) { + size = (int) strlen(data); + } + if (!count) { + count = 1; + } + *cnt = *cnt + count * size; + } + return 0; +} + +int64_t jbl_get_i64(JBL jbl) { + assert(jbl); + switch (jbl->bn.type) { + case BINN_UINT8: + return jbl->bn.vuint8; + case BINN_UINT16: + return jbl->bn.vuint16; + case BINN_UINT32: + return jbl->bn.vuint32; + case BINN_UINT64: + return jbl->bn.vuint64; + case BINN_INT8: + return jbl->bn.vint8; + case BINN_INT16: + return jbl->bn.vint16; + case BINN_INT32: + return jbl->bn.vint32; + case BINN_INT64: + return jbl->bn.vint64; + case BINN_BOOL: + return jbl->bn.vbool; + case BINN_FLOAT32: + return (int64_t) jbl->bn.vfloat; + case BINN_FLOAT64: + return (int64_t) jbl->bn.vdouble; + default: + return 0; + } +} + +int32_t jbl_get_i32(JBL jbl) { + return (int32_t) jbl_get_i64(jbl); +} + +double jbl_get_f64(JBL jbl) { + assert(jbl); + switch (jbl->bn.type) { + case BINN_FLOAT64: + return jbl->bn.vdouble; + case BINN_FLOAT32: + return jbl->bn.vfloat; + case BINN_UINT8: + return jbl->bn.vuint8; + case BINN_UINT16: + return jbl->bn.vuint16; + case BINN_UINT32: + return jbl->bn.vuint32; + case BINN_UINT64: + return jbl->bn.vuint64; + case BINN_INT8: + return jbl->bn.vint8; + case BINN_INT16: + return jbl->bn.vint16; + case BINN_INT32: + return jbl->bn.vint32; + case BINN_INT64: + return jbl->bn.vint64; + case BINN_BOOL: + return jbl->bn.vbool; + default: + return 0.0; + } +} + +const char *jbl_get_str(JBL jbl) { + assert(jbl && jbl->bn.type == BINN_STRING); + if (jbl->bn.type != BINN_STRING) { + return 0; + } else { + return jbl->bn.ptr; + } +} + +size_t jbl_copy_strn(JBL jbl, char *buf, size_t bufsz) { + assert(jbl && buf && jbl->bn.type == BINN_STRING); + if (jbl->bn.type != BINN_STRING) { + return 0; + } + size_t slen = strlen(jbl->bn.ptr); + size_t ret = MIN(slen, bufsz); + memcpy(buf, jbl->bn.ptr, ret); + return ret; +} + +jbl_type_t jbl_object_get_type(JBL jbl, const char *key) { + if (jbl->bn.type != BINN_OBJECT) { + return JBV_NONE; + } + binn bv; + if (!binn_object_get_value(&jbl->bn, key, &bv)) { + return JBV_NONE; + } + return _jbl_binn_type(bv.type); +} + +iwrc jbl_object_get_i64(JBL jbl, const char *key, int64_t *out) { + *out = 0; + if (jbl->bn.type != BINN_OBJECT) { + return JBL_ERROR_NOT_AN_OBJECT; + } + int64 v; + if (!binn_object_get_int64(&jbl->bn, key, &v)) { + return JBL_ERROR_CREATION; + } + *out = v; + return 0; +} + +iwrc jbl_object_get_f64(JBL jbl, const char *key, double *out) { + *out = 0.0; + if (jbl->bn.type != BINN_OBJECT) { + return JBL_ERROR_NOT_AN_OBJECT; + } + if (!binn_object_get_double(&jbl->bn, key, out)) { + return JBL_ERROR_CREATION; + } + return 0; +} + +iwrc jbl_object_get_bool(JBL jbl, const char *key, bool *out) { + *out = false; + if (jbl->bn.type != BINN_OBJECT) { + return JBL_ERROR_NOT_AN_OBJECT; + } + BOOL v; + if (!binn_object_get_bool(&jbl->bn, key, &v)) { + return JBL_ERROR_CREATION; + } + *out = v; + return 0; +} + +iwrc jbl_object_get_str(JBL jbl, const char *key, const char **out) { + *out = 0; + if (jbl->bn.type != BINN_OBJECT) { + return JBL_ERROR_NOT_AN_OBJECT; + } + if (!binn_object_get_str(&jbl->bn, key, (char**) out)) { + return JBL_ERROR_CREATION; + } + return 0; +} + +iwrc jbl_object_get_fill_jbl(JBL jbl, const char *key, JBL out) { + if (jbl->bn.type != BINN_OBJECT) { + return JBL_ERROR_NOT_AN_OBJECT; + } + binn_free(&out->bn); + if (!binn_object_get_value(&jbl->bn, key, &out->bn)) { + return JBL_ERROR_CREATION; + } + return 0; +} + +iwrc jbl_as_buf(JBL jbl, void **buf, size_t *size) { + assert(jbl && buf && size); + if (jbl->bn.writable && jbl->bn.dirty) { + if (!binn_save_header(&jbl->bn)) { + return JBL_ERROR_INVALID; + } + } + *buf = jbl->bn.ptr; + *size = (size_t) jbl->bn.size; + return 0; +} + +//---------------------------------------------------------------------------------------------------------- + +static iwrc _jbl_ptr_pool(const char *path, JBL_PTR *jpp, IWPOOL *pool) { + iwrc rc = 0; + int cnt = 0, len, sz, doff; + int i, j, k; + JBL_PTR jp; + char *jpr; // raw pointer to jp + *jpp = 0; + if (!path || (path[0] != '/')) { + return JBL_ERROR_JSON_POINTER; + } + for (i = 0; path[i]; ++i) { + if (path[i] == '/') { + ++cnt; + } + } + len = i; + if ((len > 1) && (path[len - 1] == '/')) { + return JBL_ERROR_JSON_POINTER; + } + sz = (int) (sizeof(struct _JBL_PTR) + cnt * sizeof(char*) + len); + if (pool) { + jp = iwpool_alloc(sz, pool); + } else { + jp = malloc(sz); + } + if (!jp) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + jpr = (char*) jp; + jp->cnt = cnt; + jp->sz = sz; + + doff = offsetof(struct _JBL_PTR, n) + cnt * sizeof(char*); + assert(sz - doff >= len); + + for (i = 0, j = 0, cnt = 0; path[i] && cnt < jp->cnt; ++i, ++j) { + if (path[i++] == '/') { + jp->n[cnt] = jpr + doff + j; + for (k = 0; ; ++i, ++k) { + if (!path[i] || (path[i] == '/')) { + --i; + *(jp->n[cnt] + k) = '\0'; + break; + } + if (path[i] == '~') { + if (path[i + 1] == '0') { + *(jp->n[cnt] + k) = '~'; + } else if (path[i + 1] == '1') { + *(jp->n[cnt] + k) = '/'; + } + ++i; + } else { + *(jp->n[cnt] + k) = path[i]; + } + } + j += k; + ++cnt; + } + } + *jpp = jp; + return rc; +} + +iwrc jbl_ptr_alloc(const char *path, JBL_PTR *jpp) { + return _jbl_ptr_pool(path, jpp, 0); +} + +iwrc jbl_ptr_alloc_pool(const char *path, JBL_PTR *jpp, IWPOOL *pool) { + return _jbl_ptr_pool(path, jpp, pool); +} + +int jbl_ptr_cmp(JBL_PTR p1, JBL_PTR p2) { + if (p1->sz != p2->sz) { + return p1->sz - p2->sz; + } + if (p1->cnt != p2->cnt) { + return p1->cnt - p2->cnt; + } + for (int i = 0; i < p1->cnt; ++i) { + int r = strcmp(p1->n[i], p2->n[i]); + if (r) { + return r; + } + } + return 0; +} + +iwrc jbl_ptr_serialize(JBL_PTR ptr, IWXSTR *xstr) { + for (int i = 0; i < ptr->cnt; ++i) { + iwrc rc = iwxstr_cat(xstr, "/", 1); + RCRET(rc); + rc = iwxstr_cat(xstr, ptr->n[i], strlen(ptr->n[i])); + RCRET(rc); + } + return 0; +} + +iwrc _jbl_visit(binn_iter *iter, int lvl, JBL_VCTX *vctx, JBL_VISITOR visitor) { + iwrc rc = 0; + binn *bn = vctx->bn; + jbl_visitor_cmd_t cmd; + int idx; + binn bv; + + if (lvl > JBL_MAX_NESTING_LEVEL) { + return JBL_ERROR_MAX_NESTING_LEVEL_EXCEEDED; + } + if (!iter) { + binn_iter it; + if (!BINN_IS_CONTAINER_TYPE(bn->type)) { + return JBL_ERROR_INVALID; + } + if (!binn_iter_init(&it, bn, bn->type)) { + return JBL_ERROR_INVALID; + } + rc = _jbl_visit(&it, 0, vctx, visitor); + return rc; + } + + switch (iter->type) { + case BINN_OBJECT: { + char key[MAX_BIN_KEY_LEN + 1]; + while (!vctx->terminate && binn_object_next(iter, key, &bv)) { + cmd = visitor(lvl, &bv, key, -1, vctx, &rc); + RCRET(rc); + if (cmd & JBL_VCMD_TERMINATE) { + vctx->terminate = true; + break; + } + if (!(cmd & JBL_VCMD_SKIP_NESTED) && BINN_IS_CONTAINER_TYPE(bv.type)) { + binn_iter it; + if (!binn_iter_init(&it, &bv, bv.type)) { + return JBL_ERROR_INVALID; + } + rc = _jbl_visit(&it, lvl + 1, vctx, visitor); + RCRET(rc); + } + } + break; + } + case BINN_MAP: { + while (!vctx->terminate && binn_map_next(iter, &idx, &bv)) { + cmd = visitor(lvl, &bv, 0, idx, vctx, &rc); + RCRET(rc); + if (cmd & JBL_VCMD_TERMINATE) { + vctx->terminate = true; + break; + } + if (!(cmd & JBL_VCMD_SKIP_NESTED) && BINN_IS_CONTAINER_TYPE(bv.type)) { + binn_iter it; + if (!binn_iter_init(&it, &bv, bv.type)) { + return JBL_ERROR_INVALID; + } + rc = _jbl_visit(&it, lvl + 1, vctx, visitor); + RCRET(rc); + } + } + break; + } + case BINN_LIST: { + for (idx = 0; !vctx->terminate && binn_list_next(iter, &bv); ++idx) { + cmd = visitor(lvl, &bv, 0, idx, vctx, &rc); + RCRET(rc); + if (cmd & JBL_VCMD_TERMINATE) { + vctx->terminate = true; + break; + } + if (!(cmd & JBL_VCMD_SKIP_NESTED) && BINN_IS_CONTAINER_TYPE(bv.type)) { + binn_iter it; + if (!binn_iter_init(&it, &bv, bv.type)) { + return JBL_ERROR_INVALID; + } + rc = _jbl_visit(&it, lvl + 1, vctx, visitor); + RCRET(rc); + } + } + break; + } + } + return rc; +} + +iwrc jbn_visit(JBL_NODE node, int lvl, JBN_VCTX *vctx, JBN_VISITOR visitor) { + iwrc rc = 0; + if (lvl > JBL_MAX_NESTING_LEVEL) { + return JBL_ERROR_MAX_NESTING_LEVEL_EXCEEDED; + } + if (!node) { + node = vctx->root; + lvl = 0; + if (!node) { + return IW_ERROR_INVALID_ARGS; + } + } + JBL_NODE n = node; + switch (node->type) { + case JBV_OBJECT: + case JBV_ARRAY: { + for (n = n->child; !vctx->terminate && n; n = n->next) { + jbn_visitor_cmd_t cmd = visitor(lvl, n, n->key, n->klidx, vctx, &rc); + RCRET(rc); + if (cmd & JBL_VCMD_TERMINATE) { + vctx->terminate = true; + } + if (cmd & JBN_VCMD_DELETE) { + JBL_NODE nn = n->next; // Keep pointer to next + _jbn_remove_item(node, n); + n->next = nn; + } else if (!(cmd & JBL_VCMD_SKIP_NESTED) && (n->type >= JBV_OBJECT)) { + rc = jbn_visit(n, lvl + 1, vctx, visitor); + RCRET(rc); + } + } + break; + } + default: + break; + } + RCRET(rc); + if (lvl == 0) { + visitor(-1, node, 0, 0, vctx, &rc); + } + return rc; +} + +IW_INLINE bool _jbl_visitor_update_jptr_cursor(JBL_VCTX *vctx, int lvl, const char *key, int idx) { + JBL_PTR jp = vctx->op; + if (lvl < jp->cnt) { + if (vctx->pos >= lvl) { + vctx->pos = lvl - 1; + } + if (vctx->pos + 1 == lvl) { + const char *keyptr; + char buf[JBNUMBUF_SIZE]; + if (key) { + keyptr = key; + } else { + iwitoa(idx, buf, JBNUMBUF_SIZE); + keyptr = buf; + } + if (!strcmp(keyptr, jp->n[lvl]) || ((jp->n[lvl][0] == '*') && (jp->n[lvl][1] == '\0'))) { + vctx->pos = lvl; + return (jp->cnt == lvl + 1); + } + } + } + return false; +} + +IW_INLINE bool _jbn_visitor_update_jptr_cursor(JBN_VCTX *vctx, int lvl, const char *key, int idx) { + JBL_PTR jp = vctx->op; + if (lvl < jp->cnt) { + if (vctx->pos >= lvl) { + vctx->pos = lvl - 1; + } + if (vctx->pos + 1 == lvl) { + const char *keyptr; + char buf[JBNUMBUF_SIZE]; + if (key) { + keyptr = key; + } else { + iwitoa(idx, buf, JBNUMBUF_SIZE); + keyptr = buf; + idx = (int) strlen(keyptr); + } + int jplen = (int) strlen(jp->n[lvl]); + if (( (idx == jplen) + && !strncmp(keyptr, jp->n[lvl], idx)) || ((jp->n[lvl][0] == '*') && (jp->n[lvl][1] == '\0') )) { + vctx->pos = lvl; + return (jp->cnt == lvl + 1); + } + } + } + return false; +} + +static jbl_visitor_cmd_t _jbl_get_visitor2(int lvl, binn *bv, const char *key, int idx, JBL_VCTX *vctx, iwrc *rc) { + JBL_PTR jp = vctx->op; + assert(jp); + if (_jbl_visitor_update_jptr_cursor(vctx, lvl, key, idx)) { // Pointer matched + JBL jbl = vctx->result; + memcpy(&jbl->bn, bv, sizeof(*bv)); + vctx->found = true; + return JBL_VCMD_TERMINATE; + } else if (jp->cnt < lvl + 1) { + return JBL_VCMD_SKIP_NESTED; + } + return JBL_VCMD_OK; +} + +static jbl_visitor_cmd_t _jbl_get_visitor(int lvl, binn *bv, const char *key, int idx, JBL_VCTX *vctx, iwrc *rc) { + JBL_PTR jp = vctx->op; + assert(jp); + if (_jbl_visitor_update_jptr_cursor(vctx, lvl, key, idx)) { // Pointer matched + JBL jbl = malloc(sizeof(struct _JBL)); + if (!jbl) { + *rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return JBL_VCMD_TERMINATE; + } + memcpy(&jbl->bn, bv, sizeof(*bv)); + vctx->result = jbl; + return JBL_VCMD_TERMINATE; + } else if (jp->cnt < lvl + 1) { + return JBL_VCMD_SKIP_NESTED; + } + return JBL_VCMD_OK; +} + +bool _jbl_at(JBL jbl, JBL_PTR jp, JBL res) { + JBL_VCTX vctx = { + .bn = &jbl->bn, + .op = jp, + .pos = -1, + .result = res + }; + _jbl_visit(0, 0, &vctx, _jbl_get_visitor2); + return vctx.found; +} + +iwrc jbl_at2(JBL jbl, JBL_PTR jp, JBL *res) { + JBL_VCTX vctx = { + .bn = &jbl->bn, + .op = jp, + .pos = -1 + }; + iwrc rc = _jbl_visit(0, 0, &vctx, _jbl_get_visitor); + if (rc) { + *res = 0; + } else { + if (!vctx.result) { + rc = JBL_ERROR_PATH_NOTFOUND; + *res = 0; + } else { + *res = (JBL) vctx.result; + } + } + return rc; +} + +iwrc jbl_at(JBL jbl, const char *path, JBL *res) { + JBL_PTR jp; + iwrc rc = _jbl_ptr_pool(path, &jp, 0); + if (rc) { + *res = 0; + return rc; + } + rc = jbl_at2(jbl, jp, res); + free(jp); + return rc; +} + +static jbn_visitor_cmd_t _jbn_get_visitor(int lvl, JBL_NODE n, const char *key, int klidx, JBN_VCTX *vctx, iwrc *rc) { + if (lvl < 0) { // EOF + return JBL_VCMD_OK; + } + JBL_PTR jp = vctx->op; + assert(jp); + if (_jbn_visitor_update_jptr_cursor(vctx, lvl, key, klidx)) { // Pointer matched + vctx->result = n; + return JBL_VCMD_TERMINATE; + } else if (jp->cnt < lvl + 1) { + return JBL_VCMD_SKIP_NESTED; + } + return JBL_VCMD_OK; +} + +iwrc jbn_at2(JBL_NODE node, JBL_PTR jp, JBL_NODE *res) { + JBN_VCTX vctx = { + .root = node, + .op = jp, + .pos = -1 + }; + iwrc rc = jbn_visit(node, 0, &vctx, _jbn_get_visitor); + if (rc) { + *res = 0; + } else { + if (!vctx.result) { + rc = JBL_ERROR_PATH_NOTFOUND; + *res = 0; + } else { + *res = (JBL_NODE) vctx.result; + } + } + return rc; +} + +iwrc jbn_at(JBL_NODE node, const char *path, JBL_NODE *res) { + JBL_PTR jp; + iwrc rc = _jbl_ptr_pool(path, &jp, 0); + if (rc) { + *res = 0; + return rc; + } + rc = jbn_at2(node, jp, res); + free(jp); + return rc; +} + +int jbn_paths_compare(JBL_NODE n1, const char *n1path, JBL_NODE n2, const char *n2path, jbl_type_t vtype, iwrc *rcp) { + *rcp = 0; + JBL_NODE v1 = 0, v2 = 0; + iwrc rc = jbn_at(n1, n1path, &v1); + if (rc && (rc != JBL_ERROR_PATH_NOTFOUND)) { + *rcp = rc; + return -2; + } + rc = jbn_at(n2, n2path, &v2); + if (rc && (rc != JBL_ERROR_PATH_NOTFOUND)) { + *rcp = rc; + return -2; + } + if (vtype) { + if (((v1 == 0) || (v1->type != vtype)) || ((v2 == 0) || (v2->type != vtype))) { + *rcp = JBL_ERROR_TYPE_MISMATCHED; + return -2; + } + } + return _jbl_compare_nodes(v1, v2, rcp); +} + +int jbn_path_compare(JBL_NODE n1, JBL_NODE n2, const char *path, jbl_type_t vtype, iwrc *rcp) { + return jbn_paths_compare(n1, path, n2, path, vtype, rcp); +} + +int jbn_path_compare_str(JBL_NODE n, const char *path, const char *sv, iwrc *rcp) { + *rcp = 0; + JBL_NODE v; + iwrc rc = jbn_at(n, path, &v); + if (rc) { + *rcp = rc; + return -2; + } + struct _JBL_NODE cn = { + .type = JBV_STR, + .vptr = sv, + .vsize = (int) strlen(sv) + }; + return _jbl_compare_nodes(v, &cn, rcp); +} + +int jbn_path_compare_i64(JBL_NODE n, const char *path, int64_t iv, iwrc *rcp) { + *rcp = 0; + JBL_NODE v; + iwrc rc = jbn_at(n, path, &v); + if (rc) { + *rcp = rc; + return -2; + } + struct _JBL_NODE cn = { + .type = JBV_I64, + .vi64 = iv + }; + return _jbl_compare_nodes(v, &cn, rcp); +} + +int jbn_path_compare_f64(JBL_NODE n, const char *path, double fv, iwrc *rcp) { + *rcp = 0; + JBL_NODE v; + iwrc rc = jbn_at(n, path, &v); + if (rc) { + *rcp = rc; + return -2; + } + struct _JBL_NODE cn = { + .type = JBV_F64, + .vf64 = fv + }; + return _jbl_compare_nodes(v, &cn, rcp); +} + +int jbn_path_compare_bool(JBL_NODE n, const char *path, bool bv, iwrc *rcp) { + *rcp = 0; + JBL_NODE v; + iwrc rc = jbn_at(n, path, &v); + if (rc) { + *rcp = rc; + return -2; + } + struct _JBL_NODE cn = { + .type = JBV_BOOL, + .vbool = bv + }; + return _jbl_compare_nodes(v, &cn, rcp); +} + +IW_INLINE void _jbl_node_reset_data(JBL_NODE target) { + jbl_type_t t = target->type; + memset(((uint8_t*) target) + offsetof(struct _JBL_NODE, child), + 0, + sizeof(struct _JBL_NODE) - offsetof(struct _JBL_NODE, child)); + target->type = t; +} + +IW_INLINE void _jbl_copy_node_data(JBL_NODE target, JBL_NODE value) { + memcpy(((uint8_t*) target) + offsetof(struct _JBL_NODE, child), + ((uint8_t*) value) + offsetof(struct _JBL_NODE, child), + sizeof(struct _JBL_NODE) - offsetof(struct _JBL_NODE, child)); +} + +iwrc _jbl_increment_node_data(JBL_NODE target, JBL_NODE value) { + if ((value->type != JBV_I64) && (value->type != JBV_F64)) { + return JBL_ERROR_PATCH_INVALID_VALUE; + } + if (target->type == JBV_I64) { + if (value->type == JBV_I64) { + target->vi64 += value->vi64; + } else { + target->vi64 += (int64_t) value->vf64; + } + return 0; + } else if (target->type == JBV_F64) { + if (value->type == JBV_F64) { + target->vf64 += value->vf64; + } else { + target->vf64 += (double) value->vi64; + } + return 0; + } else { + return JBL_ERROR_PATCH_TARGET_INVALID; + } +} + +void jbn_data(JBL_NODE node) { + _jbl_node_reset_data(node); +} + +int jbn_length(JBL_NODE node) { + int ret = 0; + for (JBL_NODE n = node->child; n; n = n->next) { + ++ret; + } + return ret; +} + +static void _jbn_add_item(JBL_NODE parent, JBL_NODE node) { + assert(parent && node); + node->next = 0; + node->prev = 0; + node->parent = parent; + if (parent->child) { + JBL_NODE prev = parent->child->prev; + parent->child->prev = node; + if (prev) { // -V1051 + prev->next = node; + node->prev = prev; + } else { + parent->child->next = node; + node->prev = parent->child; + } + } else { + parent->child = node; + } + if (parent->type == JBV_ARRAY) { + node->key = 0; + if (node->prev) { + node->klidx = node->prev->klidx + 1; + } else { + node->klidx = 0; + } + } +} + +void jbn_add_item(JBL_NODE parent, JBL_NODE node) { + _jbn_add_item(parent, node); +} + +iwrc jbn_add_item_str(JBL_NODE parent, const char *key, const char *val, int vlen, JBL_NODE *node_out, IWPOOL *pool) { + if (!parent || !pool || (parent->type < JBV_OBJECT)) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + JBL_NODE n = iwpool_calloc(sizeof(*n), pool); + if (!n) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + if (parent->type == JBV_OBJECT) { + if (!key) { + return IW_ERROR_INVALID_ARGS; + } + n->key = iwpool_strdup(pool, key, &rc); + RCGO(rc, finish); + n->klidx = (int) strlen(n->key); + } + n->type = JBV_STR; + if (val) { + if (vlen < 0) { + vlen = (int) strlen(val); + } + n->vptr = iwpool_strndup(pool, val, vlen, &rc); + RCGO(rc, finish); + n->vsize = vlen; + } + jbn_add_item(parent, n); + if (node_out) { + *node_out = n; + } +finish: + return rc; +} + +iwrc jbn_add_item_null(JBL_NODE parent, const char *key, IWPOOL *pool) { + if (!parent || !pool || (parent->type < JBV_OBJECT)) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + JBL_NODE n = iwpool_calloc(sizeof(*n), pool); + if (!n) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + if (parent->type == JBV_OBJECT) { + if (!key) { + return IW_ERROR_INVALID_ARGS; + } + n->key = iwpool_strdup(pool, key, &rc); + RCGO(rc, finish); + n->klidx = (int) strlen(n->key); + } + n->type = JBV_NULL; + jbn_add_item(parent, n); +finish: + return rc; +} + +iwrc jbn_add_item_i64(JBL_NODE parent, const char *key, int64_t val, JBL_NODE *node_out, IWPOOL *pool) { + if (!parent || !pool || (parent->type < JBV_OBJECT)) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + JBL_NODE n = iwpool_calloc(sizeof(*n), pool); + if (!n) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + if (parent->type == JBV_OBJECT) { + if (!key) { + return IW_ERROR_INVALID_ARGS; + } + n->key = iwpool_strdup(pool, key, &rc); + RCGO(rc, finish); + n->klidx = (int) strlen(n->key); + } + n->type = JBV_I64; + n->vi64 = val; + jbn_add_item(parent, n); + if (node_out) { + *node_out = n; + } +finish: + return rc; +} + +iwrc jbn_add_item_f64(JBL_NODE parent, const char *key, double val, JBL_NODE *node_out, IWPOOL *pool) { + if (!parent || !pool || (parent->type < JBV_OBJECT)) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + JBL_NODE n = iwpool_calloc(sizeof(*n), pool); + if (!n) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + if (parent->type == JBV_OBJECT) { + if (!key) { + return IW_ERROR_INVALID_ARGS; + } + n->key = iwpool_strdup(pool, key, &rc); + RCGO(rc, finish); + n->klidx = (int) strlen(n->key); + } + n->type = JBV_F64; + n->vf64 = val; + jbn_add_item(parent, n); + if (node_out) { + *node_out = n; + } +finish: + return rc; +} + +iwrc jbn_add_item_bool(JBL_NODE parent, const char *key, bool val, JBL_NODE *node_out, IWPOOL *pool) { + if (!parent || !pool || (parent->type < JBV_OBJECT)) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + JBL_NODE n = iwpool_calloc(sizeof(*n), pool); + if (!n) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + if (parent->type == JBV_OBJECT) { + if (!key) { + return IW_ERROR_INVALID_ARGS; + } + n->key = iwpool_strdup(pool, key, &rc); + RCGO(rc, finish); + n->klidx = (int) strlen(n->key); + } + n->type = JBV_BOOL; + n->vbool = val; + jbn_add_item(parent, n); + +finish: + return rc; +} + +iwrc jbn_add_item_obj(JBL_NODE parent, const char *key, JBL_NODE *out, IWPOOL *pool) { + if (!parent || !pool || (parent->type < JBV_OBJECT)) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + JBL_NODE n = iwpool_calloc(sizeof(*n), pool); + if (!n) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + if (parent->type == JBV_OBJECT) { + if (!key) { + return IW_ERROR_INVALID_ARGS; + } + n->key = iwpool_strdup(pool, key, &rc); + RCGO(rc, finish); + n->klidx = (int) strlen(n->key); + } + n->type = JBV_OBJECT; + jbn_add_item(parent, n); + if (out) { + *out = n; + } +finish: + return rc; +} + +iwrc jbn_add_item_arr(JBL_NODE parent, const char *key, JBL_NODE *out, IWPOOL *pool) { + if (!parent || !pool || (parent->type < JBV_OBJECT)) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + JBL_NODE n = iwpool_calloc(sizeof(*n), pool); + if (!n) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + if (parent->type == JBV_OBJECT) { + if (!key) { + return IW_ERROR_INVALID_ARGS; + } + n->key = iwpool_strdup(pool, key, &rc); + RCGO(rc, finish); + n->klidx = (int) strlen(n->key); + } + n->type = JBV_ARRAY; + jbn_add_item(parent, n); + if (out) { + *out = n; + } +finish: + return rc; +} + +iwrc jbn_copy_path( + JBL_NODE src, + const char *src_path, + JBL_NODE target, + const char *target_path, + bool overwrite_on_nulls, + bool no_src_clone, + IWPOOL *pool) { + if (!src || !src_path || !target || !target_path || !pool) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + JBL_NODE n1, n2; + jbp_patch_t op = JBP_REPLACE; + + if (strcmp("/", src_path) != 0) { // -V526 + rc = jbn_at(src, src_path, &n1); + if (rc == JBL_ERROR_PATH_NOTFOUND) { + return 0; + } + RCRET(rc); + } else { + n1 = src; + } + if (!overwrite_on_nulls && (n1->type <= JBV_NULL)) { + return 0; + } + if (no_src_clone) { + n2 = n1; + } else { + rc = jbn_clone(n1, &n2, pool); + RCRET(rc); + } + + rc = jbn_at(target, target_path, &n1); + if (rc == JBL_ERROR_PATH_NOTFOUND) { + rc = 0; + op = JBP_ADD_CREATE; + } + JBL_PATCH p[] = { + { + .op = op, + .path = target_path, + .vnode = n2 + } + }; + return jbn_patch(target, p, sizeof(p) / sizeof(p[0]), pool); +} + +IW_EXPORT iwrc jbn_copy_paths( + JBL_NODE src, + JBL_NODE target, + const char **paths, + bool overwrite_on_nulls, + bool no_src_clone, + IWPOOL *pool) { + if (!target || !src || !paths || !pool) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + for (const char **p = paths; *p; ++p) { + const char *path = *p; + rc = jbn_copy_path(src, path, target, path, overwrite_on_nulls, no_src_clone, pool); + RCBREAK(rc); + } + return rc; +} + +IW_INLINE void _jbn_remove_item(JBL_NODE parent, JBL_NODE child) { + assert(parent->child); + if (parent->child == child) { // First element + if (child->next) { + parent->child = child->next; + parent->child->prev = child->prev; + if (child->prev) { + child->prev->next = 0; + } + } else { + parent->child = 0; + } + } else if (parent->child->prev == child) { // Last element + parent->child->prev = child->prev; + if (child->prev) { + child->prev->next = 0; + } + } else { // Somewhere in middle + if (child->next) { + child->next->prev = child->prev; + } + if (child->prev) { + child->prev->next = child->next; + } + } + child->next = 0; + child->prev = 0; + child->child = 0; + child->parent = 0; +} + +void jbn_remove_item(JBL_NODE parent, JBL_NODE child) { + _jbn_remove_item(parent, child); +} + +static iwrc _jbl_create_node( + JBLDRCTX *ctx, + const binn *bv, + JBL_NODE parent, + const char *key, + int klidx, + JBL_NODE *node, + bool clone_strings) { + iwrc rc = 0; + JBL_NODE n = iwpool_alloc(sizeof(*n), ctx->pool); + if (node) { + *node = 0; + } + if (!n) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + memset(n, 0, sizeof(*n)); + if (key && clone_strings) { + n->key = iwpool_strndup(ctx->pool, key, klidx, &rc); + RCGO(rc, finish); + } else { + n->key = key; + } + n->klidx = klidx; + n->parent = parent; + switch (bv->type) { + case BINN_NULL: + n->type = JBV_NULL; + break; + case BINN_STRING: + n->type = JBV_STR; + if (!clone_strings) { + n->vptr = bv->ptr; + n->vsize = bv->size; + } else { + n->vptr = iwpool_strndup(ctx->pool, bv->ptr, bv->size, &rc); + n->vsize = bv->size; + RCGO(rc, finish); + } + break; + case BINN_OBJECT: + case BINN_MAP: + n->type = JBV_OBJECT; + break; + case BINN_LIST: + n->type = JBV_ARRAY; + break; + case BINN_TRUE: + n->type = JBV_BOOL; + n->vbool = true; + break; + case BINN_FALSE: + n->type = JBV_BOOL; + n->vbool = false; + break; + case BINN_BOOL: + n->type = JBV_BOOL; + n->vbool = bv->vbool; + break; + case BINN_UINT8: + n->vi64 = bv->vuint8; + n->type = JBV_I64; + break; + case BINN_UINT16: + n->vi64 = bv->vuint16; + n->type = JBV_I64; + break; + case BINN_UINT32: + n->vi64 = bv->vuint32; + n->type = JBV_I64; + break; + case BINN_UINT64: + n->vi64 = bv->vuint64; + n->type = JBV_I64; + break; + case BINN_INT8: + n->vi64 = bv->vint8; // NOLINT(bugprone-signed-char-misuse) + n->type = JBV_I64; + break; + case BINN_INT16: + n->vi64 = bv->vint16; + n->type = JBV_I64; + break; + case BINN_INT32: + n->vi64 = bv->vint32; + n->type = JBV_I64; + break; + case BINN_INT64: + n->vi64 = bv->vint64; + n->type = JBV_I64; + break; + case BINN_FLOAT32: + case BINN_FLOAT64: + n->vf64 = bv->vdouble; + n->type = JBV_F64; + break; + default: + rc = JBL_ERROR_CREATION; + goto finish; + } + if (parent) { + _jbn_add_item(parent, n); + } + +finish: + if (rc) { + free(n); + } else { + if (node) { + *node = n; + } + } + return rc; +} + +static iwrc _jbl_node_from_binn_impl( + JBLDRCTX *ctx, + const binn *bn, + JBL_NODE parent, + char *key, + int klidx, + bool clone_strings) { + binn bv; + binn_iter iter; + iwrc rc = 0; + + switch (bn->type) { + case BINN_OBJECT: + case BINN_MAP: + rc = _jbl_create_node(ctx, bn, parent, key, klidx, &parent, clone_strings); + RCRET(rc); + if (!ctx->root) { + ctx->root = parent; + } + if (!binn_iter_init(&iter, (binn*) bn, bn->type)) { + return JBL_ERROR_INVALID; + } + if (bn->type == BINN_OBJECT) { + for (int i = 0; binn_object_next2(&iter, &key, &klidx, &bv); ++i) { + rc = _jbl_node_from_binn_impl(ctx, &bv, parent, key, klidx, clone_strings); + RCRET(rc); + } + } else if (bn->type == BINN_MAP) { + for (int i = 0; binn_map_next(&iter, &klidx, &bv); ++i) { + rc = _jbl_node_from_binn_impl(ctx, &bv, parent, 0, klidx, clone_strings); + RCRET(rc); + } + } + break; + case BINN_LIST: + rc = _jbl_create_node(ctx, bn, parent, key, klidx, &parent, clone_strings); + RCRET(rc); + if (!ctx->root) { + ctx->root = parent; + } + if (!binn_iter_init(&iter, (binn*) bn, bn->type)) { + return JBL_ERROR_INVALID; + } + for (int i = 0; binn_list_next(&iter, &bv); ++i) { + rc = _jbl_node_from_binn_impl(ctx, &bv, parent, 0, i, clone_strings); + RCRET(rc); + } + break; + default: { + rc = _jbl_create_node(ctx, bn, parent, key, klidx, 0, clone_strings); + RCRET(rc); + break; + } + } + return rc; +} + +iwrc _jbl_node_from_binn(const binn *bn, JBL_NODE *node, bool clone_strings, IWPOOL *pool) { + JBLDRCTX ctx = { + .pool = pool + }; + iwrc rc = _jbl_node_from_binn_impl(&ctx, bn, 0, 0, -1, clone_strings); + if (rc) { + *node = 0; + } else { + *node = ctx.root; + } + return rc; +} + +static JBL_NODE _jbl_node_find(JBL_NODE node, JBL_PTR ptr, int from, int to) { + if (!ptr || !node) { + return 0; + } + JBL_NODE n = node; + + for (int i = from; n && i < ptr->cnt && i < to; ++i) { + if (n->type == JBV_OBJECT) { + int ptrnlen = (int) strlen(ptr->n[i]); + for (n = n->child; n; n = n->next) { + if (!strncmp(n->key, ptr->n[i], n->klidx) && (ptrnlen == n->klidx)) { + break; + } + } + } else if (n->type == JBV_ARRAY) { + int64_t idx = iwatoi(ptr->n[i]); + for (n = n->child; n; n = n->next) { + if (idx == n->klidx) { + break; + } + } + } else { + return 0; + } + } + return n; +} + +IW_INLINE JBL_NODE _jbl_node_find2(JBL_NODE node, JBL_PTR ptr) { + if (!node || !ptr || !ptr->cnt) { + return 0; + } + return _jbl_node_find(node, ptr, 0, ptr->cnt - 1); +} + +static JBL_NODE _jbl_node_detach(JBL_NODE target, JBL_PTR path) { + if (!path) { + return 0; + } + JBL_NODE parent = (path->cnt > 1) ? _jbl_node_find(target, path, 0, path->cnt - 1) : target; + if (!parent) { + return 0; + } + JBL_NODE child = _jbl_node_find(parent, path, path->cnt - 1, path->cnt); + if (!child) { + return 0; + } + _jbn_remove_item(parent, child); + return child; +} + +JBL_NODE jbn_detach2(JBL_NODE target, JBL_PTR path) { + return _jbl_node_detach(target, path); +} + +JBL_NODE jbn_detach(JBL_NODE target, const char *path) { + JBL_PTR jp; + iwrc rc = _jbl_ptr_pool(path, &jp, 0); + if (rc) { + return 0; + } + JBL_NODE res = jbn_detach2(target, jp); + free(jp); + return res; +} + +static int _jbl_cmp_node_keys(const void *o1, const void *o2) { + JBL_NODE n1 = *((JBL_NODE*) o1); + JBL_NODE n2 = *((JBL_NODE*) o2); + if (!n1 && !n2) { + return 0; + } + if (!n2 || (n1->klidx > n2->klidx)) { // -V522 + return 1; + } else if (!n1 || (n1->klidx < n2->klidx)) { // -V522 + return -1; + } + return strncmp(n1->key, n2->key, n1->klidx); +} + +static uint32_t _jbl_node_count(JBL_NODE n) { + uint32_t ret = 0; + n = n->child; + while (n) { + ret++; + n = n->next; + } + return ret; +} + +static int _jbl_compare_objects(JBL_NODE n1, JBL_NODE n2, iwrc *rcp) { + int ret = 0; + uint32_t cnt = _jbl_node_count(n1); + uint32_t i = _jbl_node_count(n2); + if (cnt > i) { + return 1; + } else if (cnt < i) { + return -1; + } else if (cnt == 0) { + return 0; + } + JBL_NODE *s1 = malloc(2 * sizeof(JBL_NODE) * cnt); + if (!s1) { + *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return 0; + } + JBL_NODE *s2 = s1 + cnt; + + i = 0; + n1 = n1->child; + n2 = n2->child; + while (n1 && n2) { + s1[i] = n1; + s2[i] = n2; + n1 = n1->next; + n2 = n2->next; + ++i; + } + qsort(s1, cnt, sizeof(JBL_NODE), _jbl_cmp_node_keys); + qsort(s2, cnt, sizeof(JBL_NODE), _jbl_cmp_node_keys); + for (i = 0; i < cnt; ++i) { + ret = _jbl_cmp_node_keys(s1 + i, s2 + i); + if (ret) { + goto finish; + } + ret = _jbl_compare_nodes(s1[i], s2[i], rcp); + if (*rcp || ret) { + goto finish; + } + } + +finish: + free(s1); + return ret; +} + +int _jbl_compare_nodes(JBL_NODE n1, JBL_NODE n2, iwrc *rcp) { + if (!n1 && !n2) { + return 0; + } else if (!n1) { + return -1; + } else if (!n2) { + return 1; + } else if (n1->type != n2->type) { + return (int) n1->type - (int) n2->type; + } + switch (n1->type) { + case JBV_BOOL: + return n1->vbool - n2->vbool; + case JBV_I64: + return n1->vi64 > n2->vi64 ? 1 : n1->vi64 < n2->vi64 ? -1 : 0; + case JBV_F64: { + size_t sz1, sz2; + char b1[JBNUMBUF_SIZE]; + char b2[JBNUMBUF_SIZE]; + jbi_ftoa(n1->vf64, b1, &sz1); + jbi_ftoa(n2->vf64, b2, &sz2); + return iwafcmp(b1, sz1, b2, sz2); + } + case JBV_STR: + if (n1->vsize != n2->vsize) { + return n1->vsize - n2->vsize; + } + return strncmp(n1->vptr, n2->vptr, n1->vsize); + case JBV_ARRAY: + for (n1 = n1->child, n2 = n2->child; n1 && n2; n1 = n1->next, n2 = n2->next) { + int res = _jbl_compare_nodes(n1, n2, rcp); + if (res) { + return res; + } + } + if (n1) { + return 1; + } else if (n2) { + return -1; + } else { + return 0; + } + case JBV_OBJECT: + return _jbl_compare_objects(n1, n2, rcp); + case JBV_NULL: + case JBV_NONE: + break; + } + return 0; +} + +int jbn_compare_nodes(JBL_NODE n1, JBL_NODE n2, iwrc *rcp) { + return _jbl_compare_nodes(n1, n2, rcp); +} + +static iwrc _jbl_target_apply_patch(JBL_NODE target, const JBL_PATCHEXT *ex, IWPOOL *pool) { + + struct _JBL_NODE *ntmp; + jbp_patch_t op = ex->p->op; + JBL_PTR path = ex->path; + JBL_NODE value = ex->p->vnode; + bool oproot = ex->path->cnt == 1 && *ex->path->n[0] == '\0'; + + if (op == JBP_TEST) { + iwrc rc = 0; + if (!value) { + return JBL_ERROR_PATCH_NOVALUE; + } + if (_jbl_compare_nodes(oproot ? target : _jbl_node_find(target, path, 0, path->cnt), value, &rc)) { + RCRET(rc); + return JBL_ERROR_PATCH_TEST_FAILED; + } else { + return rc; + } + } + if (oproot) { // Root operation + if (op == JBP_REMOVE) { + memset(target, 0, sizeof(*target)); + } else if ((op == JBP_REPLACE) || (op == JBP_ADD) || (op == JBP_ADD_CREATE)) { + if (!value) { + return JBL_ERROR_PATCH_NOVALUE; + } + memmove(target, value, sizeof(*value)); + } + } else { // Not a root + if ((op == JBP_REMOVE) || (op == JBP_REPLACE)) { + _jbl_node_detach(target, ex->path); + } + if (op == JBP_REMOVE) { + return 0; + } else if ((op == JBP_MOVE) || (op == JBP_COPY) || (op == JBP_SWAP)) { + if (op == JBP_MOVE) { + value = _jbl_node_detach(target, ex->from); + } else { + value = _jbl_node_find(target, ex->from, 0, ex->from->cnt); + } + if (!value) { + return JBL_ERROR_PATH_NOTFOUND; + } + if (op == JBP_SWAP) { + ntmp = iwpool_calloc(sizeof(*ntmp), pool); + if (!ntmp) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + } + } else { // ADD/REPLACE/INCREMENT + if (!value) { + return JBL_ERROR_PATCH_NOVALUE; + } + } + int lastidx = path->cnt - 1; + JBL_NODE parent = (path->cnt > 1) ? _jbl_node_find(target, path, 0, lastidx) : target; + if (!parent) { + if (op == JBP_ADD_CREATE) { + parent = target; + for (int i = 0; i < lastidx; ++i) { + JBL_NODE pn = _jbl_node_find(parent, path, i, i + 1); + if (!pn) { + pn = iwpool_calloc(sizeof(*pn), pool); + if (!pn) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + pn->type = JBV_OBJECT; + pn->key = path->n[i]; + pn->klidx = (int) strlen(pn->key); + _jbn_add_item(parent, pn); + } else if (pn->type != JBV_OBJECT) { + return JBL_ERROR_PATCH_TARGET_INVALID; + } + parent = pn; + } + } else { + return JBL_ERROR_PATCH_TARGET_INVALID; + } + } + if (parent->type == JBV_ARRAY) { + if ((path->n[lastidx][0] == '-') && (path->n[lastidx][1] == '\0')) { + if (op == JBP_SWAP) { + value = _jbl_node_detach(target, ex->from); + } + _jbn_add_item(parent, value); // Add to end of array + } else { // Insert into the specified index + int idx = iwatoi(path->n[lastidx]); + int cnt = idx; + JBL_NODE child = parent->child; + while (child && cnt > 0) { + cnt--; + child = child->next; + } + if (cnt > 0) { + return JBL_ERROR_PATCH_INVALID_ARRAY_INDEX; + } + value->klidx = idx; + if (child) { + if (op == JBP_SWAP) { + _jbl_copy_node_data(ntmp, value); + _jbl_copy_node_data(value, child); + _jbl_copy_node_data(child, ntmp); + } else { + value->parent = parent; + value->next = child; + value->prev = child->prev; + child->prev = value; + if (child == parent->child) { + parent->child = value; + } else { + value->prev->next = value; + } + while (child) { + child->klidx++; + child = child->next; + } + } + } else { + if (op == JBP_SWAP) { + value = _jbl_node_detach(target, ex->from); + } + _jbn_add_item(parent, value); + } + } + } else if (parent->type == JBV_OBJECT) { + JBL_NODE child = _jbl_node_find(parent, path, path->cnt - 1, path->cnt); + if (child) { + if (op == JBP_INCREMENT) { + return _jbl_increment_node_data(child, value); + } else { + if (op == JBP_SWAP) { + _jbl_copy_node_data(ntmp, value); + _jbl_copy_node_data(value, child); + _jbl_copy_node_data(child, ntmp); + } else { + _jbl_copy_node_data(child, value); + } + } + } else if (op != JBP_INCREMENT) { + if (op == JBP_SWAP) { + value = _jbl_node_detach(target, ex->from); + } + value->key = path->n[path->cnt - 1]; + value->klidx = (int) strlen(value->key); + _jbn_add_item(parent, value); + } else { + return JBL_ERROR_PATCH_TARGET_INVALID; + } + } else { + return JBL_ERROR_PATCH_TARGET_INVALID; + } + } + return 0; +} + +static iwrc _jbl_from_node_impl(binn *res, JBL_NODE node) { + iwrc rc = 0; + switch (node->type) { + case JBV_OBJECT: + if (!binn_create(res, BINN_OBJECT, 0, NULL)) { + return JBL_ERROR_CREATION; + } + for (JBL_NODE n = node->child; n; n = n->next) { + binn bv; + rc = _jbl_from_node_impl(&bv, n); + RCRET(rc); + if (!binn_object_set_value2(res, n->key, n->klidx, &bv)) { + rc = JBL_ERROR_CREATION; + } + binn_free(&bv); + RCRET(rc); + } + break; + case JBV_ARRAY: + if (!binn_create(res, BINN_LIST, 0, NULL)) { + return JBL_ERROR_CREATION; + } + for (JBL_NODE n = node->child; n; n = n->next) { + binn bv; + rc = _jbl_from_node_impl(&bv, n); + RCRET(rc); + if (!binn_list_add_value(res, &bv)) { + rc = JBL_ERROR_CREATION; + } + binn_free(&bv); + RCRET(rc); + } + break; + case JBV_STR: + binn_init_item(res); + binn_set_string(res, (void*) node->vptr, 0); + break; + case JBV_I64: + binn_init_item(res); + binn_set_int64(res, node->vi64); + break; + case JBV_F64: + binn_init_item(res); + binn_set_double(res, node->vf64); + break; + case JBV_BOOL: + binn_init_item(res); + binn_set_bool(res, node->vbool); + break; + case JBV_NULL: + binn_init_item(res); + binn_set_null(res); + break; + case JBV_NONE: + rc = JBL_ERROR_CREATION; + break; + } + return rc; +} + +iwrc _jbl_binn_from_node(binn *res, JBL_NODE node) { + iwrc rc = _jbl_from_node_impl(res, node); + if (!rc) { + if (res->writable && res->dirty) { + binn_save_header(res); + } + } + return rc; +} + +iwrc _jbl_from_node(JBL jbl, JBL_NODE node) { + jbl->node = node; + return _jbl_binn_from_node(&jbl->bn, node); +} + +static iwrc _jbl_patch_node(JBL_NODE root, const JBL_PATCH *p, size_t cnt, IWPOOL *pool) { + if (cnt < 1) { + return 0; + } + if (!root || !p) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + size_t i = 0; + JBL_PATCHEXT parr[cnt]; + memset(parr, 0, cnt * sizeof(JBL_PATCHEXT)); + for (i = 0; i < cnt; ++i) { + JBL_PATCHEXT *ext = &parr[i]; + ext->p = &p[i]; + rc = _jbl_ptr_pool(p[i].path, &ext->path, pool); + RCRET(rc); + if (p[i].from) { + rc = _jbl_ptr_pool(p[i].from, &ext->from, pool); + RCRET(rc); + } + } + for (i = 0; i < cnt; ++i) { + rc = _jbl_target_apply_patch(root, &parr[i], pool); + RCRET(rc); + } + return rc; +} + +static iwrc _jbl_patch(JBL jbl, const JBL_PATCH *p, size_t cnt, IWPOOL *pool) { + if (cnt < 1) { + return 0; + } + if (!jbl || !p) { + return IW_ERROR_INVALID_ARGS; + } + binn bv; + binn *bn; + JBL_NODE root; + iwrc rc = _jbl_node_from_binn(&jbl->bn, &root, false, pool); + RCRET(rc); + rc = _jbl_patch_node(root, p, cnt, pool); + RCRET(rc); + if (root->type != JBV_NONE) { + rc = _jbl_from_node_impl(&bv, root); + RCRET(rc); + bn = &bv; + } else { + bn = 0; + } + binn_free(&jbl->bn); + if (bn) { + if (bn->writable && bn->dirty) { + binn_save_header(bn); + } + memcpy(&jbl->bn, bn, sizeof(jbl->bn)); + jbl->bn.allocated = 0; + } else { + memset(&jbl->bn, 0, sizeof(jbl->bn)); + root->type = JBV_NONE; + } + return rc; +} + +int _jbl_cmp_atomic_values(JBL v1, JBL v2) { + jbl_type_t t1 = jbl_type(v1); + jbl_type_t t2 = jbl_type(v2); + if (t1 != t2) { + return (int) t1 - (int) t2; + } + switch (t1) { + case JBV_BOOL: + case JBV_I64: { + int64_t vv1 = jbl_get_i64(v1); + int64_t vv2 = jbl_get_i64(v2); + return vv1 > vv2 ? 1 : vv1 < vv2 ? -1 : 0; + } + case JBV_STR: + return strcmp(jbl_get_str(v1), jbl_get_str(v2)); // -V575 + case JBV_F64: { + double vv1 = jbl_get_f64(v1); + double vv2 = jbl_get_f64(v2); + return vv1 > vv2 ? 1 : vv1 < vv2 ? -1 : 0; + } + default: + return 0; + } +} + +bool _jbl_is_eq_atomic_values(JBL v1, JBL v2) { + jbl_type_t t1 = jbl_type(v1); + jbl_type_t t2 = jbl_type(v2); + if (t1 != t2) { + return false; + } + switch (t1) { + case JBV_BOOL: + case JBV_I64: + return jbl_get_i64(v1) == jbl_get_i64(v2); + case JBV_STR: + return !strcmp(jbl_get_str(v1), jbl_get_str(v2)); // -V575 + case JBV_F64: + return jbl_get_f64(v1) == jbl_get_f64(v2); // -V550 + case JBV_OBJECT: + case JBV_ARRAY: + return false; + default: + return true; + } +} + +// --------------------------- Public API + +void jbn_apply_from(JBL_NODE target, JBL_NODE from) { + const int off = offsetof(struct _JBL_NODE, child); + memcpy((char*) target + off, + (char*) from + off, + sizeof(struct _JBL_NODE) - off); +} + +iwrc jbl_to_node(JBL jbl, JBL_NODE *node, bool clone_strings, IWPOOL *pool) { + if (jbl->node) { + *node = jbl->node; + return 0; + } + return _jbl_node_from_binn(&jbl->bn, node, clone_strings, pool); +} + +iwrc jbn_patch(JBL_NODE root, const JBL_PATCH *p, size_t cnt, IWPOOL *pool) { + return _jbl_patch_node(root, p, cnt, pool); +} + +iwrc jbl_patch(JBL jbl, const JBL_PATCH *p, size_t cnt) { + if (cnt < 1) { + return 0; + } + if (!jbl || !p) { + return IW_ERROR_INVALID_ARGS; + } + IWPOOL *pool = iwpool_create(jbl->bn.size); + if (!pool) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + iwrc rc = _jbl_patch(jbl, p, cnt, pool); + iwpool_destroy(pool); + return rc; +} + +static iwrc _jbl_create_patch(JBL_NODE node, JBL_PATCH **pptr, int *cntp, IWPOOL *pool) { + *pptr = 0; + *cntp = 0; + int i = 0; + for (JBL_NODE n = node->child; n; n = n->next) { + if (n->type != JBV_OBJECT) { + return JBL_ERROR_PATCH_INVALID; + } + ++i; + } + JBL_PATCH *p = iwpool_alloc(i * sizeof(*p), pool); + if (!p) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + memset(p, 0, i * sizeof(*p)); + i = 0; + for (JBL_NODE n = node->child; n; n = n->next, ++i) { + JBL_PATCH *pp = p + i; + for (JBL_NODE n2 = n->child; n2; n2 = n2->next) { + if (!strncmp("op", n2->key, n2->klidx)) { + if (n2->type != JBV_STR) { + return JBL_ERROR_PATCH_INVALID; + } + if (!strncmp("add", n2->vptr, n2->vsize)) { + pp->op = JBP_ADD; + } else if (!strncmp("remove", n2->vptr, n2->vsize)) { + pp->op = JBP_REMOVE; + } else if (!strncmp("replace", n2->vptr, n2->vsize)) { + pp->op = JBP_REPLACE; + } else if (!strncmp("copy", n2->vptr, n2->vsize)) { + pp->op = JBP_COPY; + } else if (!strncmp("move", n2->vptr, n2->vsize)) { + pp->op = JBP_MOVE; + } else if (!strncmp("test", n2->vptr, n2->vsize)) { + pp->op = JBP_TEST; + } else if (!strncmp("increment", n2->vptr, n2->vsize)) { + pp->op = JBP_INCREMENT; + } else if (!strncmp("add_create", n2->vptr, n2->vsize)) { + pp->op = JBP_ADD_CREATE; + } else if (!strncmp("swap", n2->vptr, n2->vsize)) { + pp->op = JBP_SWAP; + } else { + return JBL_ERROR_PATCH_INVALID_OP; + } + } else if (!strncmp("value", n2->key, n2->klidx)) { + pp->vnode = n2; + } else if (!strncmp("path", n2->key, n2->klidx)) { + if (n2->type != JBV_STR) { + return JBL_ERROR_PATCH_INVALID; + } + pp->path = n2->vptr; + } else if (!strncmp("from", n2->key, n2->klidx)) { + if (n2->type != JBV_STR) { + return JBL_ERROR_PATCH_INVALID; + } + pp->from = n2->vptr; + } + } + } + *cntp = i; + *pptr = p; + return 0; +} + +iwrc jbl_patch_from_json(JBL jbl, const char *patchjson) { + if (!jbl || !patchjson) { + return IW_ERROR_INVALID_ARGS; + } + JBL_PATCH *p; + JBL_NODE patch; + int cnt = (int) strlen(patchjson); + IWPOOL *pool = iwpool_create(MAX(cnt, 1024U)); + if (!pool) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + iwrc rc = jbn_from_json(patchjson, &patch, pool); + RCGO(rc, finish); + if (patch->type == JBV_ARRAY) { + rc = _jbl_create_patch(patch, &p, &cnt, pool); + RCGO(rc, finish); + rc = _jbl_patch(jbl, p, cnt, pool); + } else if (patch->type == JBV_OBJECT) { + // FIXME: Merge patch not implemented + //_jbl_merge_patch_node() + rc = IW_ERROR_NOT_IMPLEMENTED; + } else { + rc = JBL_ERROR_PATCH_INVALID; + } + +finish: + iwpool_destroy(pool); + return rc; +} + +iwrc jbl_fill_from_node(JBL jbl, JBL_NODE node) { + if (!jbl || !node) { + return IW_ERROR_INVALID_ARGS; + } + if (node->type == JBV_NONE) { + memset(jbl, 0, sizeof(*jbl)); + return 0; + } + binn bv = { 0 }; + iwrc rc = _jbl_binn_from_node(&bv, node); + RCRET(rc); + binn_free(&jbl->bn); + memcpy(&jbl->bn, &bv, sizeof(jbl->bn)); + jbl->bn.allocated = 0; + return rc; +} + +iwrc jbl_from_node(JBL *jblp, JBL_NODE node) { + if (!jblp || !node) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + if (node->type == JBV_OBJECT) { + rc = jbl_create_empty_object(jblp); + } else if (node->type == JBV_ARRAY) { + rc = jbl_create_empty_array(jblp); + } else { + rc = IW_ERROR_INVALID_ARGS; + } + RCRET(rc); + return jbl_fill_from_node(*jblp, node); +} + +static JBL_NODE _jbl_merge_patch_node(JBL_NODE target, JBL_NODE patch, IWPOOL *pool, iwrc *rcp) { + *rcp = 0; + if (!patch) { + return 0; + } + if (patch->type == JBV_OBJECT) { + if (!target) { + target = iwpool_alloc(sizeof(*target), pool); + if (!target) { + *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return 0; + } + memset(target, 0, sizeof(*target)); + target->type = JBV_OBJECT; + target->key = patch->key; + target->klidx = patch->klidx; + } else if (target->type != JBV_OBJECT) { + _jbl_node_reset_data(target); + target->type = JBV_OBJECT; + } + patch = patch->child; + while (patch) { + JBL_NODE patch_next = patch->next; + if (patch->type == JBV_NULL) { + JBL_NODE node = target->child; + while (node) { + if ((node->klidx == patch->klidx) && !strncmp(node->key, patch->key, node->klidx)) { + _jbn_remove_item(target, node); + break; + } + node = node->next; + } + } else { + JBL_NODE node = target->child; + while (node) { + if ((node->klidx == patch->klidx) && !strncmp(node->key, patch->key, node->klidx)) { + _jbl_copy_node_data(node, _jbl_merge_patch_node(node, patch, pool, rcp)); + break; + } + node = node->next; + } + if (!node) { + _jbn_add_item(target, _jbl_merge_patch_node(0, patch, pool, rcp)); + } + } + patch = patch_next; + } + return target; + } else { + return patch; + } +} + +iwrc jbn_merge_patch_from_json(JBL_NODE root, const char *patchjson, IWPOOL *pool) { + if (!root || !patchjson || !pool) { + return IW_ERROR_INVALID_ARGS; + } + JBL_NODE patch, res; + iwrc rc = jbn_from_json(patchjson, &patch, pool); + RCRET(rc); + res = _jbl_merge_patch_node(root, patch, pool, &rc); + RCGO(rc, finish); + if (res != root) { + memcpy(root, res, sizeof(*root)); // -V575 + } + +finish: + return rc; +} + +iwrc jbl_merge_patch(JBL jbl, const char *patchjson) { + if (!jbl || !patchjson) { + return IW_ERROR_INVALID_ARGS; + } + binn bv; + JBL_NODE target; + IWPOOL *pool = iwpool_create(jbl->bn.size * 2); + if (!pool) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + iwrc rc = _jbl_node_from_binn(&jbl->bn, &target, false, pool); + RCGO(rc, finish); + rc = jbn_merge_patch_from_json(target, patchjson, pool); + RCGO(rc, finish); + + rc = _jbl_binn_from_node(&bv, target); + RCGO(rc, finish); + + binn_free(&jbl->bn); + memcpy(&jbl->bn, &bv, sizeof(jbl->bn)); + jbl->bn.allocated = 0; + +finish: + iwpool_destroy(pool); + return 0; +} + +iwrc jbl_merge_patch_jbl(JBL jbl, JBL patch) { + + IWXSTR *xstr = iwxstr_new(); + if (!xstr) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + iwrc rc = jbl_as_json(patch, jbl_xstr_json_printer, xstr, 0); + RCGO(rc, finish); + rc = jbl_merge_patch(jbl, iwxstr_ptr(xstr)); +finish: + iwxstr_destroy(xstr); + return rc; +} + +iwrc jbn_patch_auto(JBL_NODE root, JBL_NODE patch, IWPOOL *pool) { + if (!root || !patch || !pool) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + if (patch->type == JBV_OBJECT) { + _jbl_merge_patch_node(root, patch, pool, &rc); + } else if (patch->type == JBV_ARRAY) { + int cnt; + JBL_PATCH *p; + rc = _jbl_create_patch(patch, &p, &cnt, pool); + RCRET(rc); + rc = _jbl_patch_node(root, p, cnt, pool); + } else { + return IW_ERROR_INVALID_ARGS; + } + return rc; +} + +iwrc jbn_merge_patch(JBL_NODE root, JBL_NODE patch, IWPOOL *pool) { + if (!root || !patch || pool || (root->type != JBV_OBJECT)) { + return IW_ERROR_INVALID_ARGS; + } + iwrc rc = 0; + _jbl_merge_patch_node(root, patch, pool, &rc); + return rc; +} + +static const char *_jbl_ecodefn(locale_t locale, uint32_t ecode) { + if (!((ecode > _JBL_ERROR_START) && (ecode < _JBL_ERROR_END))) { + return 0; + } + switch (ecode) { + case JBL_ERROR_INVALID_BUFFER: + return "Invalid JBL buffer (JBL_ERROR_INVALID_BUFFER)"; + case JBL_ERROR_CREATION: + return "Cannot create JBL object (JBL_ERROR_CREATION)"; + case JBL_ERROR_INVALID: + return "Invalid JBL object (JBL_ERROR_INVALID)"; + case JBL_ERROR_PARSE_JSON: + return "Failed to parse JSON string (JBL_ERROR_PARSE_JSON)"; + case JBL_ERROR_PARSE_UNQUOTED_STRING: + return "Unquoted JSON string (JBL_ERROR_PARSE_UNQUOTED_STRING)"; + case JBL_ERROR_PARSE_INVALID_CODEPOINT: + return "Invalid unicode codepoint/escape sequence (JBL_ERROR_PARSE_INVALID_CODEPOINT)"; + case JBL_ERROR_PARSE_INVALID_UTF8: + return "Invalid utf8 string (JBL_ERROR_PARSE_INVALID_UTF8)"; + case JBL_ERROR_JSON_POINTER: + return "Invalid JSON pointer (rfc6901) path (JBL_ERROR_JSON_POINTER)"; + case JBL_ERROR_PATH_NOTFOUND: + return "JSON object not matched the path specified (JBL_ERROR_PATH_NOTFOUND)"; + case JBL_ERROR_PATCH_INVALID: + return "Invalid JSON patch specified (JBL_ERROR_PATCH_INVALID)"; + case JBL_ERROR_PATCH_INVALID_OP: + return "Invalid JSON patch operation specified (JBL_ERROR_PATCH_INVALID_OP)"; + case JBL_ERROR_PATCH_NOVALUE: + return "No value specified in JSON patch (JBL_ERROR_PATCH_NOVALUE)"; + case JBL_ERROR_PATCH_TARGET_INVALID: + return "Could not find target object to set value (JBL_ERROR_PATCH_TARGET_INVALID)"; + case JBL_ERROR_PATCH_INVALID_VALUE: + return "Invalid value specified by patch (JBL_ERROR_PATCH_INVALID_VALUE)"; + case JBL_ERROR_PATCH_INVALID_ARRAY_INDEX: + return "Invalid array index in JSON patch path (JBL_ERROR_PATCH_INVALID_ARRAY_INDEX)"; + case JBL_ERROR_PATCH_TEST_FAILED: + return "JSON patch test operation failed (JBL_ERROR_PATCH_TEST_FAILED)"; + case JBL_ERROR_NOT_AN_OBJECT: + return "JBL is not an object (JBL_ERROR_NOT_AN_OBJECT)"; + case JBL_ERROR_TYPE_MISMATCHED: + return "Type of JBL object mismatched user type constraints (JBL_ERROR_TYPE_MISMATCHED)"; + case JBL_ERROR_MAX_NESTING_LEVEL_EXCEEDED: + return "Exceeded the maximal object nesting level: " _STR(JBL_MAX_NESTING_LEVEL) + " (JBL_ERROR_MAX_NESTING_LEVEL_EXCEEDED)"; + } + return 0; +} + +iwrc jbl_init() { + static int _jbl_initialized = 0; + if (!__sync_bool_compare_and_swap(&_jbl_initialized, 0, 1)) { + return 0; + } + return iwlog_register_ecodefn(_jbl_ecodefn); +} diff --git a/src/jbl/jbl.h b/src/jbl/jbl.h new file mode 100644 index 0000000..516df39 --- /dev/null +++ b/src/jbl/jbl.h @@ -0,0 +1,894 @@ +#pragma once +#ifndef JBLOB_H +#define JBLOB_H + +/************************************************************************************************** + * EJDB2 + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +/** @file + * + * @brief JSON serialization and patching routines. + * + * Supported standards: + * + * - [JSON Patch](https://tools.ietf.org/html/rfc6902) + * - [JSON Merge patch](https://tools.ietf.org/html/rfc7386) + * - [JSON Path specification](https://tools.ietf.org/html/rfc6901) + * + * JSON document can be represented in three different formats: + * + * - Plain JSON text. + * + * - @ref JBL Memory compact binary format [Binn](https://github.com/liteserver/binn) + * Used for JSON serialization but lacks of data modification flexibility. + * + * - @ref JBL_NODE In memory JSON document presented as tree. Convenient for in-place + * document modification and patching. + * + * Library function allows conversion of JSON document between above formats. + */ + +#include +#include +#include +#include + +IW_EXTERN_C_START + +/** + * @brief JSON document in compact binary format [Binn](https://github.com/liteserver/binn) + */ +struct _JBL; +typedef struct _JBL*JBL; + +typedef enum { + _JBL_ERROR_START = (IW_ERROR_START + 15000UL + 1000), + JBL_ERROR_INVALID_BUFFER, /**< Invalid JBL buffer (JBL_ERROR_INVALID_BUFFER) */ + JBL_ERROR_CREATION, /**< Cannot create JBL object (JBL_ERROR_CREATION) */ + JBL_ERROR_INVALID, /**< Invalid JBL object (JBL_ERROR_INVALID) */ + JBL_ERROR_PARSE_JSON, /**< Failed to parse JSON string (JBL_ERROR_PARSE_JSON) */ + JBL_ERROR_PARSE_UNQUOTED_STRING, /**< Unquoted JSON string (JBL_ERROR_PARSE_UNQUOTED_STRING) */ + JBL_ERROR_PARSE_INVALID_CODEPOINT, + /**< Invalid unicode codepoint/escape sequence + (JBL_ERROR_PARSE_INVALID_CODEPOINT) */ + JBL_ERROR_PARSE_INVALID_UTF8, /**< Invalid utf8 string (JBL_ERROR_PARSE_INVALID_UTF8) */ + JBL_ERROR_JSON_POINTER, /**< Invalid JSON pointer (rfc6901) path (JBL_ERROR_JSON_POINTER) */ + JBL_ERROR_PATH_NOTFOUND, /**< JSON object not matched the path specified (JBL_ERROR_PATH_NOTFOUND) */ + JBL_ERROR_PATCH_INVALID, /**< Invalid JSON patch specified (JBL_ERROR_PATCH_INVALID) */ + JBL_ERROR_PATCH_INVALID_OP, /**< Invalid JSON patch operation specified (JBL_ERROR_PATCH_INVALID_OP) */ + JBL_ERROR_PATCH_NOVALUE, /**< No value specified in JSON patch (JBL_ERROR_PATCH_NOVALUE) */ + JBL_ERROR_PATCH_TARGET_INVALID, + /**< Could not find target object to set value (JBL_ERROR_PATCH_TARGET_INVALID) + */ + JBL_ERROR_PATCH_INVALID_VALUE, /**< Invalid value specified by patch (JBL_ERROR_PATCH_INVALID_VALUE) */ + JBL_ERROR_PATCH_INVALID_ARRAY_INDEX, + /**< Invalid array index in JSON patch path + (JBL_ERROR_PATCH_INVALID_ARRAY_INDEX) */ + JBL_ERROR_NOT_AN_OBJECT, /**< JBL is not an object (JBL_ERROR_NOT_AN_OBJECT) */ + JBL_ERROR_TYPE_MISMATCHED, + /**< Type of JBL object mismatched user type constraints + (JBL_ERROR_TYPE_MISMATCHED) */ + JBL_ERROR_PATCH_TEST_FAILED, /**< JSON patch test operation failed (JBL_ERROR_PATCH_TEST_FAILED) */ + JBL_ERROR_MAX_NESTING_LEVEL_EXCEEDED, + /**< Reached the maximal object nesting level: 1000 + (JBL_ERROR_MAX_NESTING_LEVEL_EXCEEDED) */ + _JBL_ERROR_END, +} jbl_ecode_t; + +typedef struct _JBL_iterator { + unsigned char *pnext; + unsigned char *plimit; + int type; + int count; + int current; +} JBL_iterator; + +typedef uint8_t jbl_print_flags_t; +#define JBL_PRINT_PRETTY ((jbl_print_flags_t) 0x01U) +#define JBL_PRINT_CODEPOINTS ((jbl_print_flags_t) 0x02U) + +typedef uint8_t jbn_visitor_cmd_t; +#define JBL_VCMD_OK ((jbn_visitor_cmd_t) 0x00U) +#define JBL_VCMD_TERMINATE ((jbn_visitor_cmd_t) 0x01U) +#define JBL_VCMD_SKIP_NESTED ((jbn_visitor_cmd_t) 0x02U) +#define JBN_VCMD_DELETE ((jbn_visitor_cmd_t) 0x04U) + +typedef enum { + JBV_NONE = 0, // Do not reorder + JBV_NULL, + JBV_BOOL, // Do not reorder + JBV_I64, + JBV_F64, + JBV_STR, + JBV_OBJECT, // Do not reorder + JBV_ARRAY, +} jbl_type_t; + +/** + * @brief JSON document as in-memory tree (DOM tree). + */ +typedef struct _JBL_NODE { + struct _JBL_NODE *next; + struct _JBL_NODE *prev; + struct _JBL_NODE *parent; /**< Optional parent */ + const char *key; + int klidx; + uint32_t flags; /**< Utility node flags */ + + // Do not sort/add members after this point (offsetof usage below) + struct _JBL_NODE *child; + int vsize; + jbl_type_t type; + union { + const char *vptr; + bool vbool; + int64_t vi64; + double vf64; + }; +} *JBL_NODE; + +/** + * @brief JSON Patch operation according to rfc6902 + */ +typedef enum { + JBP_ADD = 1, + JBP_REMOVE, + JBP_REPLACE, + JBP_COPY, + JBP_MOVE, + JBP_TEST, + // Non standard operations + JBP_INCREMENT, /**< Value increment */ + JBP_ADD_CREATE, /**< Create intermediate object nodes for missing path segments */ + JBP_SWAP, /**< Swap values of two nodes */ +} jbp_patch_t; + +/** + * @brief JSON patch specification + */ +typedef struct _JBL_PATCH { + jbp_patch_t op; + const char *path; + const char *from; + const char *vjson; + JBL_NODE vnode; +} JBL_PATCH; + +/** + * @brief JSON pointer rfc6901 + * @see jbl_ptr_alloc() + */ +typedef struct _JBL_PTR { + uint64_t op; /**< Opaque data associated with pointer */ + int cnt; /**< Number of nodes */ + int sz; /**< Size of JBL_PTR allocated area */ + char *n[1]; /**< Path nodes */ +} *JBL_PTR; + +/** Prints JSON to some oputput specified by `op` */ +typedef iwrc (*jbl_json_printer)(const char *data, int size, char ch, int count, void *op); + +/** + * @brief Create empty binary JSON object. + * + * @note `jblp` should be disposed by `jbl_destroy()` + * @see `jbl_fill_from_node()` + * @param [out] jblp Pointer to be initialized by new object. + */ +IW_EXPORT iwrc jbl_create_empty_object(JBL *jblp); + +/** + * @brief Create empty binary JSON array. + * + * @note `jblp` should be disposed by `jbl_destroy()` + * @see `jbl_fill_from_node()` + * @param [out] jblp Pointer to be initialized by new object. + */ +IW_EXPORT iwrc jbl_create_empty_array(JBL *jblp); + +/** + * @brief Sets arbitrary user data associated with JBL object. + * + * @param jbl JBL container + * @param user_data User data pointer. Optional. + * @param user_data_free_fn User data dispose function. Optional. + */ +IW_EXPORT void jbl_set_user_data(JBL jbl, void *user_data, void (*user_data_free_fn)(void*)); + +/** + * @brief Returns user data associated with given `jbl` container. + * + * @param jbl JBL container. + */ +IW_EXPORT void *jbl_get_user_data(JBL jbl); + +/** + * @brief Set integer JBL object property value + * or add a new entry to end of array JBL object. + * + * In the case when `jbl` object is array value will be added to end array. + * + * @warning `jbl` object must writable in other words created with + * `jbl_create_empty_object()` or `jbl_create_empty_array()` + * + * @param jbl JBL container + * @param key Object key. Does't makes sense for array objects. + * @param v Value to set + */ +IW_EXPORT iwrc jbl_set_int64(JBL jbl, const char *key, int64_t v); + +/** + * @brief Set double JBL object property value + * or add a new entry to end of array JBL object. + * + * In the case when `jbl` object is array value will be added to end array. + * + * @warning `jbl` object must writable in other words created with + * `jbl_create_empty_object()` or `jbl_create_empty_array()` + * + * @param jbl JBL container + * @param key Object key. Does't makes sense for array objects. + * @param v Value to set + */ +IW_EXPORT iwrc jbl_set_f64(JBL jbl, const char *key, double v); + +/** + * @brief Set string JBL object property value + * or add a new entry to end of array JBL object. + * + * In the case when `jbl` object is array value will be added to end array. + * + * @warning `jbl` object must writable in other words created with + * `jbl_create_empty_object()` or `jbl_create_empty_array()` + * + * @param jbl JBL container + * @param key Object key. Does't makes sense for array objects. + * @param v Value to set + */ +IW_EXPORT iwrc jbl_set_string(JBL jbl, const char *key, const char *v); + +IW_EXPORT iwrc jbl_set_string_printf(JBL jbl, const char *key, const char *format, ...); + +/** + * @brief Set bool JBL object property value + * or add a new entry to end of array JBL object. + * + * In the case when `jbl` object is array value will be added to end array. + * + * @warning `jbl` object must writable in other words created with + * `jbl_create_empty_object()` or `jbl_create_empty_array()` + * + * @param jbl JBL container + * @param key Object key. Does't makes sense for array objects. + * @param v Value to set + */ +IW_EXPORT iwrc jbl_set_bool(JBL jbl, const char *key, bool v); + + +/** + * @brief Set null JBL object property value + * or add a new entry to end of array JBL object. + * + * In the case when `jbl` object is array value will be added to end array. + * + * @warning `jbl` object must writable in other words created with + * `jbl_create_empty_object()` or `jbl_create_empty_array()` + * + * @param jbl JBL container + * @param key Object key. Does't makes sense for array objects. + * @param v Value to set + */ +IW_EXPORT iwrc jbl_set_null(JBL jbl, const char *key); + +IW_EXPORT iwrc jbl_set_empty_array(JBL jbl, const char *key); + +IW_EXPORT iwrc jbl_set_empty_object(JBL jbl, const char *key); + +/** + * @brief Set nested JBL object property value + * or add a new entry to end of array JBL object. + * + * In the case when `jbl` object is array value will be added to end array. + * + * @warning `jbl` object must writable in other words created with + * `jbl_create_empty_object()` or `jbl_create_empty_array()` + * + * @param jbl JBL container + * @param key Object key. Does't makes sense for array objects. + * @param v Value to set + */ +IW_EXPORT iwrc jbl_set_nested(JBL jbl, const char *key, JBL nested); + +/** + * @brief Initialize new `JBL` document by `binn` data from buffer. + * @note Created document will be allocated by `malloc()` + * and should be destroyed by `jbl_destroy()`. + * + * @param [out] jblp Pointer initialized by created JBL document. Not zero. + * @param buf Memory buffer with `binn` data. Not zero. + * @param bufsz Size of `buf` + * @param keep_on_destroy If true `buf` not will be freed by `jbl_destroy()` + */ +IW_EXPORT iwrc jbl_from_buf_keep(JBL *jblp, void *buf, size_t bufsz, bool keep_on_destroy); + +/** + * @brief Clones the given `src` JBL object into newly allocated `targetp` object. + * + * JBL object stored into `targetp` should be disposed by `jbl_destroy()`. + * + * @param src Source object to clone + * @param targetp Pointer on target object. + */ +IW_EXPORT iwrc jbl_clone(JBL src, JBL *targetp); + +/** + * @brief Copy all keys from `src` object into `target` object. + * @note Function does not care about keys duplication. + * + * @param src Source JBL object. Must be object. + * @param target Target JBL object. Must be object. + */ +IW_EXPORT iwrc jbl_object_copy_to(JBL src, JBL target); + +/** + * @brief Clones the given `src` JBL_NODE object into new `targetp` instance. + * Memory allocateted by given memor `pool` instance. + * + * @param src Source object to clone + * @param target Pointer on new instance + * @param pool Memory pool used for allocations during clone object construction + */ +IW_EXPORT iwrc jbn_clone(JBL_NODE src, JBL_NODE *targetp, IWPOOL *pool); + +/** + * @brief Assign a JSON node value from `from` node into `target` node. + * Context elements of `target` node: `parent`, `next` are not touched. + * + * @param target Node + * @param from + * @return IW_EXPORT jbn_apply_from + */ +IW_EXPORT void jbn_apply_from(JBL_NODE target, JBL_NODE from); + +/** + * @brief Copies JSON subtree under given `src_path` into `target` object under `target_path`. + * If some tree exists under `target_path` it will be replaced by copied subtree. + * + * Copied subtree will be allocated in using given memory `pool`. + * + * @param src Source JSON tree. + * @param src_path Path where copied subtree located. If src_path is `/` then `src` object itself will be cloned. + * @param target Target JSON tree. + * @param target_path Path to place copied subtree. + * @param overwrite_on_nulls If true `null` values will be copied to `src` object as well. + * @param no_src_clone If true object pointed by `src_path` object will not be cloned into `pool` before applying patch. + * It is a dangerous option if you use same memory pool for source and target objects. + * Do not set it to `true` until you clearly understand what are you doing. + * @param pool Memory pool used for allocations + */ +IW_EXPORT iwrc jbn_copy_path( + JBL_NODE src, + const char *src_path, + JBL_NODE target, + const char *target_path, + bool overwrite_on_nulls, + bool no_src_clone, + IWPOOL *pool); + +/** + * @brief Copies a set of values pointed by `paths` zero terminated array + * of `src` object into respective paths of `target` object. + * + * @param src Source object whose keys will be copied. + * @param target Target object to recieve key values of `src` obejct + * @param paths Zero terminated array of pointers to zero terminated key names. + * @param overwrite_on_nulls If true `null` values will be copied to `src` object as well. + * @param no_src_clone If true copied objects will not be cloned into given `pool` before copying. + * It is a dangerous option if you use same memory pool for source and target objects. + * Do not set it to `true` until you clearly understand what are you doing. + * @param pool Memory pool used for allocations + */ +IW_EXPORT iwrc jbn_copy_paths( + JBL_NODE src, + JBL_NODE target, + const char **paths, + bool overwrite_on_nulls, + bool no_src_clone, + IWPOOL *pool); + +/** + * @brief Clones a given `src` JBL object and stores it in memory allocated from `pool`. + * + * @param src Source object to clone + * @param targetp Pointer on target object + * @param pool Memory pool + */ +IW_EXPORT iwrc jbl_clone_into_pool(JBL src, JBL *targetp, IWPOOL *pool); + +/** + * @brief Constructs new `JBL` object from JSON string. + * @note `jblp` should be disposed by `jbl_destroy()` + * @param [out] jblp Pointer initialized by created JBL document. Not zero. + * @param jsonstr JSON string to be converted + */ +IW_EXPORT iwrc jbl_from_json(JBL *jblp, const char *jsonstr); + + +IW_EXPORT iwrc jbl_from_json_printf(JBL *jblp, const char *format, ...); + +IW_EXPORT iwrc jbl_from_json_printf_va(JBL *jblp, const char *format, va_list va); + +/** + * @brief Get type of `jbl` value. + */ +IW_EXPORT jbl_type_t jbl_type(JBL jbl); + +/** + * @brief Get number of child elements in `jbl` container (object/array) or zero. + */ +IW_EXPORT size_t jbl_count(JBL jbl); + +/** + * @brief Get size of undelying data buffer of `jbl` value passed. + */ +IW_EXPORT size_t jbl_size(JBL jbl); + +/** + * @brief Returns size of JBL underlying data structure + */ +IW_EXPORT size_t jbl_structure_size(void); + +IW_EXPORT iwrc jbl_from_buf_keep_onstack(JBL jbl, void *buf, size_t bufsz); + +/** + * @brief Interpret `jbl` value as `int32_t`. + * Returns zero if value cannot be converted. + */ +IW_EXPORT int32_t jbl_get_i32(JBL jbl); + +/** + * @brief Interpret `jbl` value as `int64_t`. + * Returns zero if value cannot be converted. + */ +IW_EXPORT int64_t jbl_get_i64(JBL jbl); + +/** + * @brief Interpret `jbl` value as `double` value. + * Returns zero if value cannot be converted. + */ +IW_EXPORT double jbl_get_f64(JBL jbl); + +/** + * @brief Interpret `jbl` value as `\0` terminated character array. + * Returns zero if value cannot be converted. + */ +IW_EXPORT const char *jbl_get_str(JBL jbl); + +IW_EXPORT iwrc jbl_object_get_i64(JBL jbl, const char *key, int64_t *out); + +IW_EXPORT iwrc jbl_object_get_f64(JBL jbl, const char *key, double *out); + +IW_EXPORT iwrc jbl_object_get_bool(JBL jbl, const char *key, bool *out); + +IW_EXPORT iwrc jbl_object_get_str(JBL jbl, const char *key, const char **out); + +IW_EXPORT iwrc jbl_object_get_fill_jbl(JBL jbl, const char *key, JBL out); + +IW_EXPORT jbl_type_t jbl_object_get_type(JBL jbl, const char *key); + +/** + * @brief Same as `jbl_get_str()` but copies at most `bufsz` into target `buf`. + * Target buffer not touched if `jbl` value cannot be converted. + */ +IW_EXPORT size_t jbl_copy_strn(JBL jbl, char *buf, size_t bufsz); + +/** + * @brief Finds value in `jbl` document pointed by rfc6901 `path` and store it into `res`. + * + * @note `res` should be disposed by `jbl_destroy()`. + * @note If value is not fount `res` will be set to zero. + * @param jbl JBL document. Not zero. + * @param path rfc6901 JSON pointer. Not zero. + * @param [out] res Output value holder + */ +IW_EXPORT iwrc jbl_at(JBL jbl, const char *path, JBL *res); + +IW_EXPORT iwrc jbn_at(JBL_NODE node, const char *path, JBL_NODE *res); + +IW_EXPORT int jbn_path_compare(JBL_NODE n1, JBL_NODE n2, const char *path, jbl_type_t vtype, iwrc *rcp); + +IW_EXPORT int jbn_paths_compare( + JBL_NODE n1, const char *n1path, JBL_NODE n2, const char *n2path, jbl_type_t vtype, + iwrc *rcp); + +IW_EXPORT int jbn_path_compare_str(JBL_NODE n, const char *path, const char *sv, iwrc *rcp); + +IW_EXPORT int jbn_path_compare_i64(JBL_NODE n, const char *path, int64_t iv, iwrc *rcp); + +IW_EXPORT int jbn_path_compare_f64(JBL_NODE n, const char *path, double fv, iwrc *rcp); + +IW_EXPORT int jbn_path_compare_bool(JBL_NODE n, const char *path, bool bv, iwrc *rcp); + +/** + * @brief @brief Finds value in `jbl` document pointed by `jp` structure and store it into `res`. + * + * @note `res` should be disposed by `jbl_destroy()`. + * @note If value is not fount `res` will be set to zero. + * @see `jbl_ptr_alloc()` + * @param jbl JBL document. Not zero. + * @param jp JSON pointer. + * @param [out] res Output value holder + */ +IW_EXPORT iwrc jbl_at2(JBL jbl, JBL_PTR jp, JBL *res); + +IW_EXPORT iwrc jbn_at2(JBL_NODE node, JBL_PTR jp, JBL_NODE *res); + +/** + * @brief Represent `jbl` document as raw data buffer. + * + * @note Caller do not require release `buf` explicitly. + * @param jbl JBL document. Not zero. + * @param [out] buf Pointer to data buffer. Not zero. + * @param [out] size Pointer to data buffer size. Not zero. + */ +IW_EXPORT iwrc jbl_as_buf(JBL jbl, void **buf, size_t *size); + +/** + * @brief Prints JBL document as JSON string. + * + * @see jbl_fstream_json_printer() + * @see jbl_xstr_json_printer() + * @see jbl_count_json_printer() + * + * @param jbl JBL document. Not zero. + * @param pt JSON printer function pointer. Not zero. + * @param op Pointer to user data for JSON printer function. + * @param pf JSON printing mode. + */ +IW_EXPORT iwrc jbl_as_json(JBL jbl, jbl_json_printer pt, void *op, jbl_print_flags_t pf); + +/** + * @brief JSON printer to stdlib `FILE*`pointer. Eg: `stderr`, `stdout` + * @param op `FILE*` pointer + */ +IW_EXPORT iwrc jbl_fstream_json_printer(const char *data, int size, char ch, int count, void *op); + +/** + * @brief JSON printer to extended string buffer `IWXSTR` + * @param op `IWXSTR*` pointer + */ +IW_EXPORT iwrc jbl_xstr_json_printer(const char *data, int size, char ch, int count, void *op); + +/** + * @brief Just counts bytes in JSON text. + * @param op `int*` Pointer to counter number. + */ +IW_EXPORT iwrc jbl_count_json_printer(const char *data, int size, char ch, int count, void *op); + +/** + * @brief Destroys JBL document and releases its heap resources. + * @note Will set `jblp` to zero. + * @param jblp Pointer holder of JBL document. Not zero. + */ +IW_EXPORT void jbl_destroy(JBL *jblp); + +/** + * @brief Initializes placeholder for jbl iteration. + * Must be freed by `jbl_destroy()` after iteration. + * @param [out] jblp Pointer to be initialized by new object. + */ +IW_EXPORT iwrc jbl_create_iterator_holder(JBL *jblp); + +/** + * @brief Initialize allocated iterator over given `jbl` object. + * + * @param jbl JBL object to iterate + * @param iter Iterator state placeholder allocated by `jbl_create_iter_placeholder()` + */ +IW_EXPORT iwrc jbl_iterator_init(JBL jbl, JBL_iterator *iter); + +/** + * @brief Get next value from JBL_iterator. + * Returns `false` if iteration is over. + * + * @param iter Iterator object. + * @param holder Holder to object pointed by current iteration. + * @param pkey Key value holder. Zero in the case of iteration over array. + * @param klen Key length or array index in the case of iteration over array. + */ +IW_EXPORT bool jbl_iterator_next(JBL_iterator *iter, JBL holder, char **pkey, int *klen); + +//--- JBL_NODE + +/** + * @brief Converts `jbl` value to `JBL_NODE` tree. + * @note `node` resources will be released when `pool` destroyed. + * + * @param jbl JSON document in compact `binn` format. Not zero. + * @param [out] node Holder of new `JBL_NODE` value. Not zero. + * @param clone_strings If `true` JSON keys and string values will be cloned into given `pool` + * otherwise only pointers to strings will be assigned. + * Use `true` if you want to be completely safe when given `jbl` + * object will be destroyed. + * @param pool Memory used to allocate new `JBL_NODE` tree. Not zero. + */ +IW_EXPORT iwrc jbl_to_node(JBL jbl, JBL_NODE *node, bool clone_strings, IWPOOL *pool); + +/** + * @brief Converts `json` text to `JBL_NODE` tree. + * @note `node` resources will be released when `pool` destroyed. + * + * @param json JSON text + * @param [out] node Holder of new `JBL_NODE` value. Not zero. + * @param pool Memory used to allocate new `JBL_NODE` tree. Not zero. + */ +IW_EXPORT iwrc jbn_from_json(const char *json, JBL_NODE *node, IWPOOL *pool); + +IW_EXPORT iwrc jbn_from_json_printf(JBL_NODE *node, IWPOOL *pool, const char *format, ...); + +IW_EXPORT iwrc jbn_from_json_printf_va(JBL_NODE *node, IWPOOL *pool, const char *format, va_list va); + +/** + * @brief Prints JBL_NODE document as JSON string. + * + * @see jbl_fstream_json_printer() + * @see jbl_xstr_json_printer() + * @see jbl_count_json_printer() + * + * @param node `JBL_NODE` document. Not zero. + * @param pt JSON printer function. Not zero. + * @param op Pointer to user data for JSON printer function. + * @param pf JSON printing mode. + */ +IW_EXPORT iwrc jbn_as_json(JBL_NODE node, jbl_json_printer pt, void *op, jbl_print_flags_t pf); + +// Depreacted. Use: jbn_as_json +IW_EXPORT IW_DEPRECATED iwrc jbl_node_as_json(JBL_NODE node, jbl_json_printer pt, void *op, jbl_print_flags_t pf); + +// Depreacted. Use: jbn_as_json +IW_EXPORT IW_DEPRECATED iwrc jbl_node_from_json(const char *json, JBL_NODE *node, IWPOOL *pool); + +/** + * @brief Fill `jbl` document by data from `node`. + * + * Common use case: + * Create empty document with `jbl_create_empty_object()` `jbl_create_empty_array()` + * then fill it with `jbl_fill_from_node()` + * + * @param jbl JBL document to be filled. Not zero. + * @param node Source tree node. Not zero. + */ +IW_EXPORT iwrc jbl_fill_from_node(JBL jbl, JBL_NODE node); + + +/** + * @brief Converts `node` object into JBL form. + * + * @param jblp JBL pointer holder. Not zero. + * @param node Source tree node. Not zero. + * @return IW_EXPORT jbl_from_node + */ +IW_EXPORT iwrc jbl_from_node(JBL *jblp, JBL_NODE node); + +/** + * @brief Compares JSON tree nodes. + * + * - Primitive JSON values compared as is. + * - JSON arrays compared by values held in the same position in array. + * - JSON objects compared by corresponding values held under lexicographically sorted keys. + * + * @param n1 + * @param n2 + * @param [out] rcp + * + * @return - Not zero if `n1` and `n2` have different types. + * - Zero if `n1` and `n2` are equal. + * - Greater than zero if `n1` greater than `n2` + * - Lesser than zero if `n1` lesser than `n2` + */ +IW_EXPORT int jbn_compare_nodes(JBL_NODE n1, JBL_NODE n2, iwrc *rcp); + +/** + * @brief Add item to the `parent` container. + */ +IW_EXPORT void jbn_add_item(JBL_NODE parent, JBL_NODE node); + +/** + * @brief Adds string JSON node to the given `parent` node. + * Key and value are copied into allocated node. + * + * @param parent Parent holder. + * @param key Child node key cloned into node. Can be zero if parent is an array. + * @param val Child node value copied. + * @param vlen Langth of child node value. + * If `vlen` is lesser then zero length of `val` will be determined my `strlen`. + * @param node_out Optional placeholder for new node. + * @param pool Allocation pool. + */ +IW_EXPORT iwrc jbn_add_item_str( + JBL_NODE parent, const char *key, const char *val, int vlen, JBL_NODE *node_out, + IWPOOL *pool); + +/** + * @brief Adds null JSON value to the given `parent` node. + * + * @param parent Parent holder. + * @param key Child node key cloned into node. Can be zero if parent is an array. + * @param pool Allocation pool. + */ +IW_EXPORT iwrc jbn_add_item_null(JBL_NODE parent, const char *key, IWPOOL *pool); + +/** + * @brief Adds integer JSON node to the given `parent` node. + * + * @param parent Parent holder. + * @param key Child node key cloned into node. Can be zero if parent is an array. + * @param val Integer value. + * @param node_out Optional placeholder for new node. + * @param pool Allocation pool. + */ +IW_EXPORT iwrc jbn_add_item_i64(JBL_NODE parent, const char *key, int64_t val, JBL_NODE *node_out, IWPOOL *pool); + +/** + * @brief Adds fp number JSON node to the given `parent` node. + * + * @param parent Parent holder. + * @param key Child node key cloned into node. Can be zero if parent is an array. + * @param val Floating point value. + * @param node_out Optional placeholder for new node. + * @param pool Allocation pool. + */ +IW_EXPORT iwrc jbn_add_item_f64(JBL_NODE parent, const char *key, double val, JBL_NODE *node_out, IWPOOL *pool); + +/** + * @brief Add nested object under the given `key` + * + * @param parent Parent holder + * @param key Child node key cloned into node. Can be zero if parent is an array. + * @param node_out [out] Pointer to new node, can be zero. + * @param pool Allocation pool + * @return IW_EXPORT jbn_add_item_obj + */ +IW_EXPORT iwrc jbn_add_item_obj(JBL_NODE parent, const char *key, JBL_NODE *node_out, IWPOOL *pool); + +/** + * @brief Add nested array under the given `key` + * + * @param parent Parent holder + * @param key Child node key cloned into node. Can be zero if parent is an array. + * @param node_out [out] Pointer to new node, can be zero. + * @param pool Allocation pool + * @return IW_EXPORT jbn_add_item_obj + */ +IW_EXPORT iwrc jbn_add_item_arr(JBL_NODE parent, const char *key, JBL_NODE *node_out, IWPOOL *pool); + +/** + * @brief Adds boolean JSON node to the given `parent` node. + * + * @param parent Parent holder. + * @param key Child node key cloned into node. Can be zero if parent is an array. + * @param val Boolean node value. + * @param node_out [out] Pointer to new node, can be zero. + * @param pool Allocation pool. + */ +IW_EXPORT iwrc jbn_add_item_bool(JBL_NODE parent, const char *key, bool val, JBL_NODE *node_out, IWPOOL *pool); + +/** + * @brief Add item from the `parent` container. + */ +IW_EXPORT void jbn_remove_item(JBL_NODE parent, JBL_NODE child); + +/** + * @brief Remove subtree from `target` node pointed by `path` + */ +IW_EXPORT JBL_NODE jbn_detach2(JBL_NODE target, JBL_PTR path); + +IW_EXPORT JBL_NODE jbn_detach(JBL_NODE target, const char *path); + +/** + * @brief Reset tree `node` data. + */ +IW_EXPORT void jbn_data(JBL_NODE node); + +/** + * @brief Returns number of child elements of given node. + * + * @param node JBL node + */ +IW_EXPORT int jbn_length(JBL_NODE node); + +/** + * @brief Parses rfc6901 JSON path. + * @note `jpp` structure should be disposed by `free()`. + * + * @param path JSON path string. Not zero. + * @param [out] jpp Holder for parsed path structure. Not zero. + */ +IW_EXPORT iwrc jbl_ptr_alloc(const char *path, JBL_PTR *jpp); + +/** + * @brief Parses rfc6901 JSON path. + * + * @param path JSON path string. Not zero. + * @param [out] jpp JSON path string. Not zero. + * @param pool Pool used for memory allocation. Not zero. + */ +IW_EXPORT iwrc jbl_ptr_alloc_pool(const char *path, JBL_PTR *jpp, IWPOOL *pool); + +/** + * @brief Compare JSON pointers. + */ +IW_EXPORT int jbl_ptr_cmp(JBL_PTR p1, JBL_PTR p2); + +/** + * @brief Serialize JSON pointer to as text. + * @param ptr JSON pointer. Not zero. + * @param xstr Output string buffer. Not zero. + */ +IW_EXPORT iwrc jbl_ptr_serialize(JBL_PTR ptr, IWXSTR *xstr); + +/** + * @brief JBL_NODE visitor context + */ +typedef struct _JBN_VCTX { + JBL_NODE root; /**< Root node from which started visitor */ + void *op; /**< Arbitrary opaque data */ + void *result; + IWPOOL *pool; /**< Pool placeholder, initialization is responsibility of `JBN_VCTX` creator */ + int pos; /**< Aux position, not actually used by visitor core */ + bool terminate; /**< It `true` document traversal will be terminated immediately. */ +} JBN_VCTX; + +/** + * Call with lvl: `-1` means end of visiting whole object tree. + */ +typedef jbn_visitor_cmd_t (*JBN_VISITOR)(int lvl, JBL_NODE n, const char *key, int klidx, JBN_VCTX *vctx, iwrc *rc); + +IW_EXPORT iwrc jbn_visit(JBL_NODE node, int lvl, JBN_VCTX *vctx, JBN_VISITOR visitor); + +//--- PATCHING + +IW_EXPORT iwrc jbn_patch_auto(JBL_NODE root, JBL_NODE patch, IWPOOL *pool); + +IW_EXPORT iwrc jbn_merge_patch(JBL_NODE root, JBL_NODE patch, IWPOOL *pool); + +IW_EXPORT iwrc jbn_patch(JBL_NODE root, const JBL_PATCH *patch, size_t cnt, IWPOOL *pool); + +IW_EXPORT iwrc jbn_merge_patch_from_json(JBL_NODE root, const char *patchjson, IWPOOL *pool); + +IW_EXPORT iwrc jbl_patch(JBL jbl, const JBL_PATCH *patch, size_t cnt); + +IW_EXPORT iwrc jbl_patch_from_json(JBL jbl, const char *patchjson); + +IW_EXPORT iwrc jbl_merge_patch(JBL jbl, const char *patchjson); + +IW_EXPORT iwrc jbl_merge_patch_jbl(JBL jbl, JBL patch); + + +IW_EXPORT iwrc jbl_init(void); + +IW_EXTERN_C_END +#endif diff --git a/src/jbl/jbl_internal.h b/src/jbl/jbl_internal.h new file mode 100644 index 0000000..8790143 --- /dev/null +++ b/src/jbl/jbl_internal.h @@ -0,0 +1,91 @@ +#pragma once +#ifndef JBL_INTERNAL_H +#define JBL_INTERNAL_H + +/************************************************************************************************** + * EJDB2 + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +#include "jbl.h" +#include "binn.h" +#include +#include +#include +#include "ejdb2cfg.h" + +#define JBL_MAX_NESTING_LEVEL 999 + +struct _JBL { + binn bn; + JBL_NODE node; +}; + +/** + * @brief JBL visitor context + */ +typedef struct _JBL_VCTX { + binn *bn; /**< Root node from which started visitor */ + void *op; /**< Arbitrary opaque data */ + void *result; + IWPOOL *pool; /**< Pool placeholder, initialization is responsibility of `JBL_VCTX` creator */ + int pos; /**< Aux position, not actually used by visitor core */ + bool terminate; + bool found; /**< Used in _jbl_at() */ +} JBL_VCTX; + +typedef jbn_visitor_cmd_t jbl_visitor_cmd_t; + +typedef struct _JBL_PATCHEXT { + const JBL_PATCH *p; + JBL_PTR path; + JBL_PTR from; +} JBL_PATCHEXT; + +typedef struct _JBLDRCTX { + IWPOOL *pool; + JBL_NODE root; +} JBLDRCTX; + +iwrc jbl_from_buf_keep_onstack(JBL jbl, void *buf, size_t bufsz); +iwrc jbl_from_buf_keep_onstack2(JBL jbl, void *buf); + +iwrc _jbl_write_double(double num, jbl_json_printer pt, void *op); +iwrc _jbl_write_int(int64_t num, jbl_json_printer pt, void *op); +iwrc _jbl_write_string(const char *str, int len, jbl_json_printer pt, void *op, jbl_print_flags_t pf); +iwrc _jbl_node_from_binn(const binn *bn, JBL_NODE *node, bool clone_strings, IWPOOL *pool); +iwrc _jbl_binn_from_node(binn *res, JBL_NODE node); +iwrc _jbl_from_node(JBL jbl, JBL_NODE node); +bool _jbl_at(JBL jbl, JBL_PTR jp, JBL res); +int _jbl_compare_nodes(JBL_NODE n1, JBL_NODE n2, iwrc *rcp); + +typedef jbl_visitor_cmd_t (*JBL_VISITOR)(int lvl, binn *bv, const char *key, int idx, JBL_VCTX *vctx, iwrc *rc); +iwrc _jbl_visit(binn_iter *iter, int lvl, JBL_VCTX *vctx, JBL_VISITOR visitor); + +bool _jbl_is_eq_atomic_values(JBL v1, JBL v2); +int _jbl_cmp_atomic_values(JBL v1, JBL v2); + +BOOL binn_read_next_pair2(int expected_type, binn_iter *iter, int *klidx, char **pkey, binn *value); + +#endif diff --git a/src/jbl/jbl_json.c b/src/jbl/jbl_json.c new file mode 100644 index 0000000..7997601 --- /dev/null +++ b/src/jbl/jbl_json.c @@ -0,0 +1,601 @@ +#include "jbl.h" +#include "utf8proc.h" +#include "jbl_internal.h" +#include + +#define IS_WHITESPACE(c_) ((unsigned char) (c_) <= (unsigned char) ' ') + +/** JSON parsing context */ +typedef struct JCTX { + IWPOOL *pool; + JBL_NODE root; + const char *buf; + const char *sp; + iwrc rc; +} JCTX; + +static void _jbn_add_item(JBL_NODE parent, JBL_NODE node) { + assert(parent && node); + node->next = 0; + node->parent = parent; + if (parent->child) { + JBL_NODE prev = parent->child->prev; + parent->child->prev = node; + if (prev) { // -V1051 + prev->next = node; + node->prev = prev; + } else { + parent->child->next = node; + node->prev = parent->child; + } + } else { + parent->child = node; + } + if (parent->type == JBV_ARRAY) { + if (node->prev) { + node->klidx = node->prev->klidx + 1; + } else { + node->klidx = 0; + } + } +} + +static JBL_NODE _jbl_json_create_node(jbl_type_t type, const char *key, int klidx, JBL_NODE parent, JCTX *ctx) { + + JBL_NODE node = iwpool_calloc(sizeof(*node), ctx->pool); + if (!node) { + ctx->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return 0; + } + node->type = type; + node->key = key; + node->klidx = klidx; + if (parent) { + _jbn_add_item(parent, node); + } + if (!ctx->root) { + ctx->root = node; + } + return node; +} + +IW_INLINE void _jbl_skip_bom(JCTX *ctx) { + const char *p = ctx->buf; + if ((p[0] == '\xEF') && (p[1] == '\xBB') && (p[2] == '\xBF')) { + ctx->buf += 3; + } +} + +IW_INLINE int _jbl_hex(char c) { + if ((c >= '0') && (c <= '9')) { + return c - '0'; + } + if ((c >= 'a') && (c <= 'f')) { + return c - 'a' + 10; + } + if ((c >= 'A') && (c <= 'F')) { + return c - 'A' + 10; + } + return -1; +} + +static int _jbl_unescape_json_string(const char *p, char *d, int dlen, const char **end, iwrc *rcp) { + *rcp = 0; + char c; + char *ds = d; + char *de = d + dlen; + + while ((c = *p++)) { + if (c == '"') { // string closing quotes + if (end) { + *end = p; + } + return d - ds; + } else if (c == '\\') { + switch (*p) { + case '\\': + case '/': + case '"': + if (d < de) { + *d = *p; + } + ++p, ++d; + break; + case 'b': + if (d < de) { + *d = '\b'; + } + ++p, ++d; + break; + case 'f': + if (d < de) { + *d = '\f'; + } + ++p, ++d; + break; + case 'n': + case 'r': + if (d < de) { + *d = '\n'; + } + ++p, ++d; + break; + case 't': + if (d < de) { + *d = '\t'; + } + ++p, ++d; + break; + case 'u': { + uint32_t cp, cp2; + int h1, h2, h3, h4; + if ( ((h1 = _jbl_hex(p[1])) < 0) || ((h2 = _jbl_hex(p[2])) < 0) + || ((h3 = _jbl_hex(p[3])) < 0) || ((h4 = _jbl_hex(p[4])) < 0)) { + *rcp = JBL_ERROR_PARSE_INVALID_CODEPOINT; + return 0; + } + cp = h1 << 12 | h2 << 8 | h3 << 4 | h4; + if ((cp & 0xfc00) == 0xd800) { + p += 6; + if ( (p[-1] != '\\') || (*p != 'u') + || ((h1 = _jbl_hex(p[1])) < 0) || ((h2 = _jbl_hex(p[2])) < 0) + || ((h3 = _jbl_hex(p[3])) < 0) || ((h4 = _jbl_hex(p[4])) < 0)) { + *rcp = JBL_ERROR_PARSE_INVALID_CODEPOINT; + return 0; + } + cp2 = h1 << 12 | h2 << 8 | h3 << 4 | h4; + if ((cp2 & 0xfc00) != 0xdc00) { + *rcp = JBL_ERROR_PARSE_INVALID_CODEPOINT; + return 0; + } + cp = 0x10000 + ((cp - 0xd800) << 10) + (cp2 - 0xdc00); + } + if (!utf8proc_codepoint_valid(cp)) { + *rcp = JBL_ERROR_PARSE_INVALID_CODEPOINT; + return 0; + } + uint8_t uchars[4]; + utf8proc_ssize_t ulen = utf8proc_encode_char(cp, uchars); + assert(ulen <= sizeof(uchars)); + for (int i = 0; i < ulen; ++i) { + if (d < de) { + *d = uchars[i]; + } + ++d; + } + p += 5; + break; + } + default: + if (d < de) { + *d = c; + } + ++d; + } + } else { + if (d < de) { + *d = c; + } + ++d; + } + } + *rcp = JBL_ERROR_PARSE_UNQUOTED_STRING; + return 0; +} + +static const char *_jbl_parse_key(const char **key, const char *p, JCTX *ctx) { + char c; + *key = ""; + while ((c = *p++)) { + if (c == '"') { + int len = _jbl_unescape_json_string(p, 0, 0, 0, &ctx->rc); + if (ctx->rc) { + return 0; + } + if (len) { + char *kptr = iwpool_alloc(len + 1, ctx->pool); + if (!kptr) { + ctx->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return 0; + } + if ((len != _jbl_unescape_json_string(p, kptr, len, &p, &ctx->rc)) || ctx->rc) { + if (!ctx->rc) { + ctx->rc = JBL_ERROR_PARSE_JSON; + } + return 0; + } + kptr[len] = '\0'; + *key = kptr; + } + while (*p && IS_WHITESPACE(*p)) p++; + if (*p == ':') { + return p + 1; + } + ctx->rc = JBL_ERROR_PARSE_JSON; + return 0; + } else if (c == '}') { + return p - 1; + } else if (IS_WHITESPACE(c) || (c == ',')) { + continue; + } else { + ctx->rc = JBL_ERROR_PARSE_JSON; + return 0; + } + } + ctx->rc = JBL_ERROR_PARSE_JSON; + return 0; +} + +static const char *_jbl_parse_value( + int lvl, + JBL_NODE parent, + const char *key, int klidx, + const char *p, + JCTX *ctx) { + + if (lvl > JBL_MAX_NESTING_LEVEL) { + ctx->rc = JBL_ERROR_MAX_NESTING_LEVEL_EXCEEDED; + return 0; + } + + JBL_NODE node; + while (1) { + switch (*p) { + case '\0': + ctx->rc = JBL_ERROR_PARSE_JSON; + return 0; + case ' ': + case '\t': + case '\n': + case '\r': + case ',': + ++p; + break; + case 'n': + if (!strncmp(p, "null", 4)) { + _jbl_json_create_node(JBV_NULL, key, klidx, parent, ctx); + if (ctx->rc) { + return 0; + } + return p + 4; + } + ctx->rc = JBL_ERROR_PARSE_JSON; + return 0; + case 't': + if (!strncmp(p, "true", 4)) { + node = _jbl_json_create_node(JBV_BOOL, key, klidx, parent, ctx); + if (ctx->rc) { + return 0; + } + node->vbool = true; // -V522 + return p + 4; + } + ctx->rc = JBL_ERROR_PARSE_JSON; + return 0; + case 'f': + if (!strncmp(p, "false", 5)) { + node = _jbl_json_create_node(JBV_BOOL, key, klidx, parent, ctx); + if (ctx->rc) { + return 0; + } + node->vbool = false; + return p + 5; + } + ctx->rc = JBL_ERROR_PARSE_JSON; + return 0; + case '"': + ++p; + const char *end; + int len = _jbl_unescape_json_string(p, 0, 0, &end, &ctx->rc); + if (ctx->rc) { + return 0; + } + node = _jbl_json_create_node(JBV_STR, key, klidx, parent, ctx); + if (ctx->rc) { + return 0; + } + if (len) { + char *vptr = iwpool_alloc(len + 1, ctx->pool); + if (!vptr) { + ctx->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return 0; + } + if ((len != _jbl_unescape_json_string(p, vptr, len, &p, &ctx->rc)) || ctx->rc) { + if (!ctx->rc) { + ctx->rc = JBL_ERROR_PARSE_JSON; + } + return 0; + } + vptr[len] = '\0'; + node->vptr = vptr; + node->vsize = len; + } else { + p = end; + node->vptr = ""; + node->vsize = 0; + } + return p; + case '{': + node = _jbl_json_create_node(JBV_OBJECT, key, klidx, parent, ctx); + if (ctx->rc) { + return 0; + } + ++p; + while (1) { + const char *nkey; + p = _jbl_parse_key(&nkey, p, ctx); + if (ctx->rc) { + return 0; + } + if (*p == '}') { + return p + 1; // -V522 + } + p = _jbl_parse_value(lvl + 1, node, nkey, strlen(nkey), p, ctx); + if (ctx->rc) { + return 0; + } + } + break; + case '[': + node = _jbl_json_create_node(JBV_ARRAY, key, klidx, parent, ctx); + if (ctx->rc) { + return 0; + } + ++p; + for (int i = 0; ; ++i) { + p = _jbl_parse_value(lvl + 1, node, 0, i, p, ctx); + if (ctx->rc) { + return 0; + } + if (*p == ']') { + return p + 1; + } + } + break; + case ']': + return p; + break; + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + node = _jbl_json_create_node(JBV_I64, key, klidx, parent, ctx); + if (ctx->rc) { + return 0; + } + char *pe; + node->vi64 = strtoll(p, &pe, 0); + if ((pe == p) || (errno == ERANGE)) { + ctx->rc = JBL_ERROR_PARSE_JSON; + return 0; + } + if ((*pe == '.') || (*pe == 'e') || (*pe == 'E')) { + node->type = JBV_F64; + node->vf64 = strtod(p, &pe); + if ((pe == p) || (errno == ERANGE)) { + ctx->rc = JBL_ERROR_PARSE_JSON; + return 0; + } + } + return pe; + } + default: + ctx->rc = JBL_ERROR_PARSE_JSON; + return 0; + } + } + return p; +} + +static iwrc _jbl_node_as_json(JBL_NODE node, jbl_json_printer pt, void *op, int lvl, jbl_print_flags_t pf) { + iwrc rc = 0; + bool pretty = pf & JBL_PRINT_PRETTY; + +#define PT(data_, size_, ch_, count_) do { \ + rc = pt(data_, size_, ch_, count_, op); \ + RCRET(rc); \ +} while (0) + + switch (node->type) { + case JBV_ARRAY: + PT(0, 0, '[', 1); + if (node->child && pretty) { + PT(0, 0, '\n', 1); + } + for (JBL_NODE n = node->child; n; n = n->next) { + if (pretty) { + PT(0, 0, ' ', lvl + 1); + } + rc = _jbl_node_as_json(n, pt, op, lvl + 1, pf); + RCRET(rc); + if (n->next) { + PT(0, 0, ',', 1); + } + if (pretty) { + PT(0, 0, '\n', 1); + } + } + if (node->child && pretty) { + PT(0, 0, ' ', lvl); + } + PT(0, 0, ']', 1); + break; + case JBV_OBJECT: + PT(0, 0, '{', 1); + if (node->child && pretty) { + PT(0, 0, '\n', 1); + } + for (JBL_NODE n = node->child; n; n = n->next) { + if (pretty) { + PT(0, 0, ' ', lvl + 1); + } + rc = _jbl_write_string(n->key, n->klidx, pt, op, pf); + RCRET(rc); + if (pretty) { + PT(": ", -1, 0, 0); + } else { + PT(0, 0, ':', 1); + } + rc = _jbl_node_as_json(n, pt, op, lvl + 1, pf); + RCRET(rc); + if (n->next) { + PT(0, 0, ',', 1); + } + if (pretty) { + PT(0, 0, '\n', 1); + } + } + if (node->child && pretty) { + PT(0, 0, ' ', lvl); + } + PT(0, 0, '}', 1); + break; + case JBV_STR: + rc = _jbl_write_string(node->vptr, node->vsize, pt, op, pf); + break; + case JBV_I64: + rc = _jbl_write_int(node->vi64, pt, op); + break; + case JBV_F64: + rc = _jbl_write_double(node->vf64, pt, op); + break; + case JBV_BOOL: + if (node->vbool) { + PT("true", 4, 0, 1); + } else { + PT("false", 5, 0, 1); + } + break; + case JBV_NULL: + PT("null", 4, 0, 1); + break; + default: + iwlog_ecode_error3(IW_ERROR_ASSERTION); + return IW_ERROR_ASSERTION; + } +#undef PT + return rc; +} + +static JBL_NODE _jbl_clone_node_struct(JBL_NODE src, IWPOOL *pool) { + iwrc rc; + JBL_NODE n = iwpool_calloc(sizeof(*n), pool); + if (!n) { + return 0; + } + n->vsize = src->vsize; + n->type = src->type; + n->klidx = src->klidx; + n->flags = src->flags; + + if (src->key) { + n->key = iwpool_strndup(pool, src->key, src->klidx, &rc); + if (!n->key) { + return 0; + } + } + switch (src->type) { + case JBV_STR: { + n->vptr = iwpool_strndup(pool, src->vptr, src->vsize, &rc); + if (!n->vptr) { + return 0; + } + break; + } + case JBV_I64: + n->vi64 = src->vi64; + break; + case JBV_BOOL: + n->vbool = src->vbool; + break; + case JBV_F64: + n->vf64 = src->vf64; + break; + default: + break; + } + ; + return n; +} + +static jbn_visitor_cmd_t _jbl_clone_node_visit( + int lvl, JBL_NODE n, const char *key, int klidx, JBN_VCTX *vctx, + iwrc *rc) { + if (lvl < 0) { + return JBL_VCMD_OK; + } + JBL_NODE parent = vctx->root; + if (lvl < vctx->pos) { // Pop + for ( ; lvl < vctx->pos; --vctx->pos) { + parent = parent->parent; + assert(parent); + } + vctx->root = parent; + assert(vctx->root); + } else if (lvl > vctx->pos) { // Push + vctx->pos = lvl; + parent = vctx->op; + vctx->root = parent; + assert(parent); + } + JBL_NODE nn = _jbl_clone_node_struct(n, vctx->pool); + if (!nn) { + *rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return JBL_VCMD_TERMINATE; + } + _jbn_add_item(parent, nn); + if (nn->type >= JBV_OBJECT) { + vctx->op = nn; // Remeber the last container object + } + return JBL_VCMD_OK; +} + +iwrc jbn_clone(JBL_NODE src, JBL_NODE *targetp, IWPOOL *pool) { + *targetp = 0; + JBL_NODE n = _jbl_clone_node_struct(src, pool); + if (!n) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + JBN_VCTX vctx = { + .pool = pool, + .root = n, + .op = n + }; + iwrc rc = jbn_visit(src, 0, &vctx, _jbl_clone_node_visit); + RCRET(rc); + *targetp = n; + return 0; +} + +iwrc jbn_as_json(JBL_NODE node, jbl_json_printer pt, void *op, jbl_print_flags_t pf) { + return _jbl_node_as_json(node, pt, op, 0, pf); +} + +iwrc jbn_from_json(const char *json, JBL_NODE *node, IWPOOL *pool) { + *node = 0; + JCTX ctx = { + .pool = pool, + .buf = json + }; + _jbl_skip_bom(&ctx); + _jbl_parse_value(0, 0, 0, 0, ctx.buf, &ctx); + *node = ctx.root; + return ctx.rc; +} + +// Depreacated +iwrc jbl_node_from_json(const char *json, JBL_NODE *node, IWPOOL *pool) { + return jbn_from_json(json, node, pool); +} + +// Depreacated +iwrc jbl_node_as_json(JBL_NODE node, jbl_json_printer pt, void *op, jbl_print_flags_t pf) { + return _jbl_node_as_json(node, pt, op, 0, pf); +} diff --git a/src/jbl/tests/CMakeLists.txt b/src/jbl/tests/CMakeLists.txt new file mode 100644 index 0000000..1ac06b0 --- /dev/null +++ b/src/jbl/tests/CMakeLists.txt @@ -0,0 +1,20 @@ +link_libraries(ejdb2_s ${CUNIT_LIBRARIES}) +include_directories(${CUNIT_INCLUDE_DIRS}) + +file(GLOB datafiles RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/data" "data/*") +foreach (file ${datafiles}) + configure_file("data/${file}" "data/${file}" COPYONLY) +endforeach () + +set(TEST_DATA_DIR ${CMAKE_CURRENT_BINARY_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${TEST_DATA_DIR}) + +foreach (TN IN ITEMS jbl_test1 + jbl_test_binn1 + jbl_test_binn2) + add_executable(${TN} ${TN}.c) + set_target_properties(${TN} PROPERTIES + COMPILE_FLAGS "-DIW_STATIC") + add_test(NAME ${TN} WORKING_DIRECTORY ${TEST_DATA_DIR} + COMMAND ${TEST_TOOL_CMD} $) +endforeach () diff --git a/src/jbl/tests/data/001.expected.json b/src/jbl/tests/data/001.expected.json new file mode 100644 index 0000000..d3426de --- /dev/null +++ b/src/jbl/tests/data/001.expected.json @@ -0,0 +1,36 @@ +{ + "str": "𝌆", + "str1": "15øC 3đ", + "str2": "Mа二𐌂", + "str3": "привет", + "na": [ + 0.1, + 0.000006, + 0.000001 + ], + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": [ + "GML", + "XML", + true, + false + ] + }, + "GlossSee": "markup" + } + } + } + } +} \ No newline at end of file diff --git a/src/jbl/tests/data/001.json b/src/jbl/tests/data/001.json new file mode 100644 index 0000000..ccdcd70 --- /dev/null +++ b/src/jbl/tests/data/001.json @@ -0,0 +1,36 @@ +{ + "str": "\uD834\uDF06", + "str1": "15\u00f8C 3\u0111", + "str2": "\u004d\u0430\u4e8c\ud800\udf02", + "str3": "привет", + "na": [ + 0.1000, + 6E-06, + 1E-06 + ], + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": [ + "GML", + "XML", + true, + false + ] + }, + "GlossSee": "markup" + } + } + } + } +} \ No newline at end of file diff --git a/src/jbl/tests/data/002.expected.json b/src/jbl/tests/data/002.expected.json new file mode 100644 index 0000000..3e62934 --- /dev/null +++ b/src/jbl/tests/data/002.expected.json @@ -0,0 +1,36 @@ +{ + "str": "\uD834\uDF06", + "str1": "15\u00F8C 3\u0111", + "str2": "M\u0430\u4E8C\uD800\uDF02", + "str3": "\u043F\u0440\u0438\u0432\u0435\u0442", + "na": [ + 0.00012, + 0.000006, + 0.000001 + ], + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": [ + "GML", + "XML", + true, + false + ] + }, + "GlossSee": "markup" + } + } + } + } +} \ No newline at end of file diff --git a/src/jbl/tests/data/002.json b/src/jbl/tests/data/002.json new file mode 100644 index 0000000..2dea984 --- /dev/null +++ b/src/jbl/tests/data/002.json @@ -0,0 +1,36 @@ +{ + "str": "\uD834\uDF06", + "str1": "15\u00f8C 3\u0111", + "str2": "\u004d\u0430\u4e8c\ud800\udf02", + "str3": "привет", + "na": [ + 0.00011999999999999999, + 6E-06, + 1E-06 + ], + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": [ + "GML", + "XML", + true, + false + ] + }, + "GlossSee": "markup" + } + } + } + } +} \ No newline at end of file diff --git a/src/jbl/tests/data/003.expected.json b/src/jbl/tests/data/003.expected.json new file mode 100644 index 0000000..2fd06e5 --- /dev/null +++ b/src/jbl/tests/data/003.expected.json @@ -0,0 +1,3 @@ +{ + "empty": "" +} \ No newline at end of file diff --git a/src/jbl/tests/data/003.json b/src/jbl/tests/data/003.json new file mode 100644 index 0000000..5f12ebf --- /dev/null +++ b/src/jbl/tests/data/003.json @@ -0,0 +1,3 @@ +{ + "empty": "" +} \ No newline at end of file diff --git a/src/jbl/tests/data/004.expected.json b/src/jbl/tests/data/004.expected.json new file mode 100644 index 0000000..43f0446 --- /dev/null +++ b/src/jbl/tests/data/004.expected.json @@ -0,0 +1,100 @@ +{ + "web-app": { + "servlet": [ + { + "servlet-name": "cofaxCDS", + "servlet-class": "org.cofax.cds.CDSServlet", + "init-param": { + "configGlossary:installationAt": "Philadelphia, PA", + "configGlossary:adminEmail": "ksm@pobox.com", + "configGlossary:poweredBy": "Cofax", + "configGlossary:poweredByIcon": "/images/cofax.gif", + "configGlossary:staticPath": "/content/static", + "templateProcessorClass": "org.cofax.WysiwygTemplate", + "templateLoaderClass": "org.cofax.FilesTemplateLoader", + "templatePath": "templates", + "templateOverridePath": "", + "defaultListTemplate": "listTemplate.htm", + "defaultFileTemplate": "articleTemplate.htm", + "useJSP": false, + "jspListTemplate": "listTemplate.jsp", + "jspFileTemplate": "articleTemplate.jsp", + "cachePackageTagsTrack": 200, + "cachePackageTagsStore": 200, + "cachePackageTagsRefresh": 60, + "cacheTemplatesTrack": 100, + "cacheTemplatesStore": 50, + "cacheTemplatesRefresh": 15, + "cachePagesTrack": 200, + "cachePagesStore": 100, + "cachePagesRefresh": 10, + "cachePagesDirtyRead": 10, + "searchEngineListTemplate": "forSearchEnginesList.htm", + "searchEngineFileTemplate": "forSearchEngines.htm", + "searchEngineRobotsDb": "WEB-INF/robots.db", + "useDataStore": true, + "dataStoreClass": "org.cofax.SqlDataStore", + "redirectionClass": "org.cofax.SqlRedirection", + "dataStoreName": "cofax", + "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", + "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", + "dataStoreUser": "sa", + "dataStorePassword": "dataStoreTestQuery", + "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", + "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", + "dataStoreInitConns": 10, + "dataStoreMaxConns": 100, + "dataStoreConnUsageLimit": 100, + "dataStoreLogLevel": "debug", + "maxUrlLength": 500 + } + }, + { + "servlet-name": "cofaxEmail", + "servlet-class": "org.cofax.cds.EmailServlet", + "init-param": { + "mailHost": "mail1", + "mailHostOverride": "mail2" + } + }, + { + "servlet-name": "cofaxAdmin", + "servlet-class": "org.cofax.cds.AdminServlet" + }, + { + "servlet-name": "fileServlet", + "servlet-class": "org.cofax.cds.FileServlet" + }, + { + "servlet-name": "cofaxTools", + "servlet-class": "org.cofax.cms.CofaxToolsServlet", + "init-param": { + "templatePath": "toolstemplates/", + "log": 1, + "logLocation": "/usr/local/tomcat/logs/CofaxTools.log", + "logMaxSize": "", + "dataLog": 1, + "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", + "dataLogMaxSize": "", + "removePageCache": "/content/admin/remove?cache=pages&id=", + "removeTemplateCache": "/content/admin/remove?cache=templates&id=", + "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", + "lookInContext": 1, + "adminGroupID": 4, + "betaServer": true + } + } + ], + "servlet-mapping": { + "cofaxCDS": "/", + "cofaxEmail": "/cofaxutil/aemail/*", + "cofaxAdmin": "/admin/*", + "fileServlet": "/static/*", + "cofaxTools": "/tools/*" + }, + "taglib": { + "taglib-uri": "cofax.tld", + "taglib-location": "/WEB-INF/tlds/cofax.tld" + } + } +} \ No newline at end of file diff --git a/src/jbl/tests/data/004.json b/src/jbl/tests/data/004.json new file mode 100644 index 0000000..1ee80eb --- /dev/null +++ b/src/jbl/tests/data/004.json @@ -0,0 +1,100 @@ +{ + "web-app": { + "servlet": [ + { + "servlet-name": "cofaxCDS", + "servlet-class": "org.cofax.cds.CDSServlet", + "init-param": { + "configGlossary:installationAt": "Philadelphia, PA", + "configGlossary:adminEmail": "ksm@pobox.com", + "configGlossary:poweredBy": "Cofax", + "configGlossary:poweredByIcon": "/images/cofax.gif", + "configGlossary:staticPath": "/content/static", + "templateProcessorClass": "org.cofax.WysiwygTemplate", + "templateLoaderClass": "org.cofax.FilesTemplateLoader", + "templatePath": "templates", + "templateOverridePath": "", + "defaultListTemplate": "listTemplate.htm", + "defaultFileTemplate": "articleTemplate.htm", + "useJSP": false, + "jspListTemplate": "listTemplate.jsp", + "jspFileTemplate": "articleTemplate.jsp", + "cachePackageTagsTrack": 200, + "cachePackageTagsStore": 200, + "cachePackageTagsRefresh": 60, + "cacheTemplatesTrack": 100, + "cacheTemplatesStore": 50, + "cacheTemplatesRefresh": 15, + "cachePagesTrack": 200, + "cachePagesStore": 100, + "cachePagesRefresh": 10, + "cachePagesDirtyRead": 10, + "searchEngineListTemplate": "forSearchEnginesList.htm", + "searchEngineFileTemplate": "forSearchEngines.htm", + "searchEngineRobotsDb": "WEB-INF/robots.db", + "useDataStore": true, + "dataStoreClass": "org.cofax.SqlDataStore", + "redirectionClass": "org.cofax.SqlRedirection", + "dataStoreName": "cofax", + "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", + "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", + "dataStoreUser": "sa", + "dataStorePassword": "dataStoreTestQuery", + "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", + "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", + "dataStoreInitConns": 10, + "dataStoreMaxConns": 100, + "dataStoreConnUsageLimit": 100, + "dataStoreLogLevel": "debug", + "maxUrlLength": 500 + } + }, + { + "servlet-name": "cofaxEmail", + "servlet-class": "org.cofax.cds.EmailServlet", + "init-param": { + "mailHost": "mail1", + "mailHostOverride": "mail2" + } + }, + { + "servlet-name": "cofaxAdmin", + "servlet-class": "org.cofax.cds.AdminServlet" + }, + { + "servlet-name": "fileServlet", + "servlet-class": "org.cofax.cds.FileServlet" + }, + { + "servlet-name": "cofaxTools", + "servlet-class": "org.cofax.cms.CofaxToolsServlet", + "init-param": { + "templatePath": "toolstemplates/", + "log": 1, + "logLocation": "/usr/local/tomcat/logs/CofaxTools.log", + "logMaxSize": "", + "dataLog": 1, + "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", + "dataLogMaxSize": "", + "removePageCache": "/content/admin/remove?cache=pages&id=", + "removeTemplateCache": "/content/admin/remove?cache=templates&id=", + "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", + "lookInContext": 1, + "adminGroupID": 4, + "betaServer": true + } + } + ], + "servlet-mapping": { + "cofaxCDS": "/", + "cofaxEmail": "/cofaxutil/aemail/*", + "cofaxAdmin": "/admin/*", + "fileServlet": "/static/*", + "cofaxTools": "/tools/*" + }, + "taglib": { + "taglib-uri": "cofax.tld", + "taglib-location": "/WEB-INF/tlds/cofax.tld" + } + } +} \ No newline at end of file diff --git a/src/jbl/tests/data/005.expected.json b/src/jbl/tests/data/005.expected.json new file mode 100644 index 0000000..3e1a73a --- /dev/null +++ b/src/jbl/tests/data/005.expected.json @@ -0,0 +1,17 @@ +{ + "foo": "b\"ar", + "num1": 1223, + "n\"um2": 10.1226222, + "list": [ + 3, + 2.1, + 1, + "one", + "two", + {}, + { + "z": false, + "t": true + } + ] +} \ No newline at end of file diff --git a/src/jbl/tests/data/005.json b/src/jbl/tests/data/005.json new file mode 100644 index 0000000..5323a4c --- /dev/null +++ b/src/jbl/tests/data/005.json @@ -0,0 +1 @@ +{"foo": "b\"ar", "num1":1223,"n\"um2":10.1226222, "list":[3,2.1,1,"one", "two", {}, {"z":false, "t":true}]} diff --git a/src/jbl/tests/jbl_test1.c b/src/jbl/tests/jbl_test1.c new file mode 100644 index 0000000..c8900d2 --- /dev/null +++ b/src/jbl/tests/jbl_test1.c @@ -0,0 +1,900 @@ +#include "ejdb2.h" +#include +#include + +#include "jbl.h" +#include "jbl_internal.h" + +#include + +int init_suite(void) { + int rc = ejdb_init(); + return rc; +} + +int clean_suite(void) { + return 0; +} + +void _jbl_test1_1(int num, iwrc expected, jbl_print_flags_t pf) { + iwrc rc; + char path[64]; + char path_expected[64]; + JBL_NODE node = 0; + IWPOOL *pool; + char *data; + char *edata = 0; + IWXSTR *res = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(res); + + snprintf(path, sizeof(path), "data%c%03d.json", IW_PATH_CHR, num); + snprintf(path_expected, sizeof(path_expected), "data%c%03d.expected.json", IW_PATH_CHR, num); + data = iwu_file_read_as_buf(path); + CU_ASSERT_PTR_NOT_NULL_FATAL(data); + + pool = iwpool_create(1024); + CU_ASSERT_PTR_NOT_NULL_FATAL(pool); + + rc = jbn_from_json(data, &node, pool); + if (rc) { + iwlog_ecode_error3(rc); + } + CU_ASSERT_EQUAL_FATAL(rc, expected); + CU_ASSERT_PTR_NOT_NULL_FATAL(node); + if (expected) { + goto finish; + } + + rc = jbn_as_json(node, jbl_xstr_json_printer, res, pf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + edata = iwu_file_read_as_buf(path_expected); + CU_ASSERT_PTR_NOT_NULL_FATAL(edata); + + FILE *f1 = fopen("f1.txt", "w"); + FILE *f2 = fopen("f2.txt", "w"); + fprintf(f1, "\n%s", edata); + fprintf(f2, "\n%s", iwxstr_ptr(res)); + fclose(f1); + fclose(f2); + + fprintf(stderr, "ED %s\n", edata); + fprintf(stderr, "ED %s\n", iwxstr_ptr(res)); + + CU_ASSERT_EQUAL_FATAL(strcmp(edata, iwxstr_ptr(res)), 0); + +finish: + if (edata) { + free(edata); + } + free(data); + iwpool_destroy(pool); + iwxstr_destroy(res); +} + +void jbl_test1_1() { + _jbl_test1_1(1, 0, JBL_PRINT_PRETTY); + _jbl_test1_1(2, 0, JBL_PRINT_PRETTY | JBL_PRINT_CODEPOINTS); + _jbl_test1_1(3, 0, JBL_PRINT_PRETTY); + _jbl_test1_1(4, 0, JBL_PRINT_PRETTY); + _jbl_test1_1(5, 0, JBL_PRINT_PRETTY); +} + +void jbl_test1_2() { + const char *data = "{\"foo\": \"b\\\"ar\", \"num1\":1223," + "\"n\\\"um2\":10.1226222, " + "\"list\":[3,2.1,1,\"one\", \"two\", " + "{}, {\"z\":false, \"t\":true}]}"; + JBL jbl; + iwrc rc = jbl_from_json(&jbl, data); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + IWXSTR *xstr = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(xstr); + + rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, false); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + int res = strcmp(iwxstr_ptr(xstr), + "{\"foo\":\"b\\\"ar\",\"num1\":1223,\"n\\\"um2\":10.1226222," + "\"list\":[3,2.1,1,\"one\",\"two\",{},{\"z\":false,\"t\":true}]}"); + CU_ASSERT_EQUAL(res, 0); + jbl_destroy(&jbl); + + // + rc = jbl_from_json(&jbl, "{ "); + CU_ASSERT_EQUAL_FATAL(rc, JBL_ERROR_PARSE_JSON); + + iwxstr_destroy(xstr); +} + +void jbl_test1_3() { + JBL_PTR jp; + iwrc rc = jbl_ptr_alloc("/", &jp); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_EQUAL(jp->cnt, 1); + CU_ASSERT_TRUE(*jp->n[0] == '\0') + free(jp); + + rc = jbl_ptr_alloc("/foo", &jp); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_EQUAL(jp->cnt, 1); + CU_ASSERT_FALSE(strcmp(jp->n[0], "foo")); + free(jp); + + rc = jbl_ptr_alloc("/foo/bar", &jp); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_EQUAL(jp->cnt, 2); + CU_ASSERT_FALSE(strcmp(jp->n[0], "foo")); + CU_ASSERT_FALSE(strcmp(jp->n[1], "bar")); + free(jp); + + rc = jbl_ptr_alloc("/foo/bar/0/baz", &jp); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_EQUAL(jp->cnt, 4); + CU_ASSERT_FALSE(strcmp(jp->n[0], "foo")); + CU_ASSERT_FALSE(strcmp(jp->n[1], "bar")); + CU_ASSERT_FALSE(strcmp(jp->n[2], "0")); + CU_ASSERT_FALSE(strcmp(jp->n[3], "baz")); + free(jp); + + rc = jbl_ptr_alloc("/foo/b~0ar/0/b~1az", &jp); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_EQUAL(jp->cnt, 4); + CU_ASSERT_FALSE(strcmp(jp->n[0], "foo")); + CU_ASSERT_FALSE(strcmp(jp->n[1], "b~ar")); + CU_ASSERT_FALSE(strcmp(jp->n[2], "0")); + CU_ASSERT_FALSE(strcmp(jp->n[3], "b/az")); + free(jp); + + rc = jbl_ptr_alloc("/foo/", &jp); + CU_ASSERT_EQUAL(rc, JBL_ERROR_JSON_POINTER); + free(jp); + + rc = jbl_ptr_alloc("//", &jp); + CU_ASSERT_EQUAL(rc, JBL_ERROR_JSON_POINTER); + free(jp); + + rc = jbl_ptr_alloc("", &jp); + CU_ASSERT_EQUAL(rc, JBL_ERROR_JSON_POINTER); + free(jp); + + rc = jbl_ptr_alloc("~", &jp); + CU_ASSERT_EQUAL(rc, JBL_ERROR_JSON_POINTER); + free(jp); +} + +void jbl_test1_4() { + // { "foo": "bar", + // "foo2": { + // "foo3": { + // "foo4": "bar4" + // }, + // "foo5": "bar5" + // }, + // "num1": 1, + // "list1": ["one", "two", {"three": 3}] + // } + char *data + = iwu_replace_char( + strdup("{'foo':'bar','foo2':{'foo3':{'foo4':'bar4'},'foo5':'bar5'}," + "'num1':1,'list1':['one','two',{'three':3}]}"), + '\'', '"'); + JBL jbl, at, at2; + const char *sval; + int ival; + iwrc rc = jbl_from_json(&jbl, data); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbl_at(jbl, "/foo", &at); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL_FATAL(at); + + sval = jbl_get_str(at); + CU_ASSERT_PTR_NOT_NULL_FATAL(sval); + CU_ASSERT_STRING_EQUAL(sval, "bar"); + jbl_destroy(&at); + + rc = jbl_at(jbl, "/foo2/foo3", &at); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL_FATAL(at); + CU_ASSERT_TRUE(at->bn.type == BINN_OBJECT); + rc = jbl_at(at, "/foo4", &at2); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL_FATAL(at2); + sval = jbl_get_str(at2); + CU_ASSERT_PTR_NOT_NULL_FATAL(sval); + CU_ASSERT_STRING_EQUAL(sval, "bar4"); + jbl_destroy(&at2); + jbl_destroy(&at); + + at = (void*) 1; + rc = jbl_at(jbl, "/foo2/foo10", &at); + CU_ASSERT_EQUAL(rc, JBL_ERROR_PATH_NOTFOUND); + CU_ASSERT_PTR_NULL(at); + rc = 0; + + rc = jbl_at(jbl, "/foo2/*/foo4", &at); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL_FATAL(at); + sval = jbl_get_str(at); + CU_ASSERT_PTR_NOT_NULL_FATAL(sval); + CU_ASSERT_STRING_EQUAL(sval, "bar4"); + jbl_destroy(&at); + + rc = jbl_at(jbl, "/list1/1", &at); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL_FATAL(at); + sval = jbl_get_str(at); + CU_ASSERT_STRING_EQUAL(sval, "two"); + jbl_destroy(&at); + + rc = jbl_at(jbl, "/list1/2/three", &at); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL_FATAL(at); + ival = jbl_get_i32(at); + CU_ASSERT_EQUAL(ival, 3); + jbl_destroy(&at); + + rc = jbl_at(jbl, "/list1/*/three", &at); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL_FATAL(at); + ival = jbl_get_i32(at); + CU_ASSERT_EQUAL(ival, 3); + jbl_destroy(&at); + + rc = jbl_at(jbl, "/list1/*/*", &at); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL_FATAL(at); + ival = jbl_get_i32(at); + CU_ASSERT_EQUAL(ival, 3); + jbl_destroy(&at); + + jbl_destroy(&jbl); + free(data); +} + +void jbl_test1_5() { + IWXSTR *xstr = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(xstr); + + // { "foo": "bar", + // "foo2": { + // "foo3": { + // "foo4": "bar4" + // }, + // "foo5": "bar5" + // }, + // "num1": 1, + // "list1": ["one", "two", {"three": 3}] + // } + char *data + = iwu_replace_char( + strdup("{'foo':'bar','foo2':{'foo3':{'foo4':'bar4'},'foo5':'bar5'}," + "'num1':1,'list1':['one','two',{'three':3}]}"), + '\'', '"'); + JBL jbl; + int res = 0; + + // Remove ROOT + JBL_PATCH p1[] = { { .op = JBP_REMOVE, .path = "/" } }; + iwrc rc = jbl_from_json(&jbl, data); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jbl_patch(jbl, p1, sizeof(p1) / sizeof(p1[0])); + CU_ASSERT_EQUAL_FATAL(rc, 0); + jbl_destroy(&jbl); + + + // Remove "/foo" + JBL_PATCH p2[] = { { .op = JBP_REMOVE, .path = "/foo" } }; + rc = jbl_from_json(&jbl, data); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jbl_patch(jbl, p2, sizeof(p2) / sizeof(p2[0])); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, false); + CU_ASSERT_EQUAL_FATAL(rc, 0); + res = strcmp(iwxstr_ptr( + xstr), + "{\"foo2\":{\"foo3\":{\"foo4\":\"bar4\"},\"foo5\":\"bar5\"},\"num1\":1,\"list1\":[\"one\",\"two\",{\"three\":3}]}"); + CU_ASSERT_EQUAL(res, 0); + jbl_destroy(&jbl); + iwxstr_clear(xstr); + + // Remove /foo2/foo3/foo4 + // Remove /list1/1 + JBL_PATCH p3[] = { + { .op = JBP_REMOVE, .path = "/foo2/foo3/foo4" }, + { .op = JBP_REMOVE, .path = "/list1/1" } + }; + rc = jbl_from_json(&jbl, data); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jbl_patch(jbl, p3, sizeof(p3) / sizeof(p3[0])); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, false); + CU_ASSERT_EQUAL_FATAL(rc, 0); + res = strcmp(iwxstr_ptr(xstr), + "{\"foo\":\"bar\",\"foo2\":{\"foo3\":{},\"foo5\":\"bar5\"},\"num1\":1,\"list1\":[\"one\",{\"three\":3}]}"); + CU_ASSERT_EQUAL(res, 0); + jbl_destroy(&jbl); + iwxstr_clear(xstr); + iwxstr_destroy(xstr); + free(data); +} + +void apply_patch(const char *data, const char *patch, const char *result, IWXSTR *xstr, iwrc *rcp) { + CU_ASSERT_TRUE_FATAL(data && patch && xstr && rcp); + JBL jbl = 0; + char *data2 = iwu_replace_char(strdup(data), '\'', '"'); + char *patch2 = iwu_replace_char(strdup(patch), '\'', '"'); + char *result2 = result ? iwu_replace_char(strdup(result), '\'', '"') : 0; + CU_ASSERT_TRUE_FATAL(data2 && patch2); + + iwrc rc = jbl_from_json(&jbl, data2); + RCGO(rc, finish); + + rc = jbl_patch_from_json(jbl, patch2); + RCGO(rc, finish); + + rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, false); + RCGO(rc, finish); + + if (result2) { + CU_ASSERT_STRING_EQUAL(result2, iwxstr_ptr(xstr)); + } + +finish: + if (data2) { + free(data2); + } + if (patch2) { + free(patch2); + } + if (result2) { + free(result2); + } + if (jbl) { + jbl_destroy(&jbl); + } + *rcp = rc; +} + +void apply_merge_patch(const char *data, const char *patch, const char *result, IWXSTR *xstr, iwrc *rcp) { + CU_ASSERT_TRUE_FATAL(data && patch && xstr && rcp); + JBL jbl = 0; + char *data2 = iwu_replace_char(strdup(data), '\'', '"'); + char *patch2 = iwu_replace_char(strdup(patch), '\'', '"'); + char *result2 = result ? iwu_replace_char(strdup(result), '\'', '"') : 0; + CU_ASSERT_TRUE_FATAL(data2 && patch2); + + iwrc rc = jbl_from_json(&jbl, data2); + RCGO(rc, finish); + + rc = jbl_merge_patch(jbl, patch2); + RCGO(rc, finish); + + rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, false); + RCGO(rc, finish); + + if (result2) { + CU_ASSERT_STRING_EQUAL(result2, iwxstr_ptr(xstr)); + } + +finish: + if (data2) { + free(data2); + } + if (patch2) { + free(patch2); + } + if (result2) { + free(result2); + } + if (jbl) { + jbl_destroy(&jbl); + } + *rcp = rc; +} + +// Run tests: https://github.com/json-patch/json-patch-tests/blob/master/spec_tests.json +void jbl_test1_6() { + iwrc rc; + IWXSTR *xstr = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(xstr); + + apply_patch("{'foo':'bar','foo2':{'foo3':{'foo4':'bar4'},'foo5':'bar5'},'num1':1,'list1':['one','two',{'three':3}]}", + "[{'op':'remove', 'path':'/foo'}]", + "{'foo2':{'foo3':{'foo4':'bar4'},'foo5':'bar5'},'num1':1,'list1':['one','two',{'three':3}]}", + xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + // 4.1. add with missing object + apply_patch("{ 'q': { 'bar': 2 } }", + "[ {'op': 'add', 'path': '/a/b', 'value': 1} ]", + 0, xstr, &rc); + CU_ASSERT_EQUAL(rc, JBL_ERROR_PATCH_TARGET_INVALID); + iwxstr_clear(xstr); + + // A.1. Adding an Object Member + apply_patch("{'foo': 'bar'}", + "[ { 'op': 'add', 'path': '/baz', 'value': 'qux' } ]", + "{'foo':'bar','baz':'qux'}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + // A.2. Adding an Array Element + apply_patch("{'foo': [ 'bar', 'baz' ]}", + "[{ 'op': 'add', 'path': '/foo/1', 'value': 'qux' }]", + "{'foo':['bar','qux','baz']}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + // A.3. Removing an Object Member + apply_patch("{'baz': 'qux','foo': 'bar'}", + "[{ 'op': 'remove', 'path': '/baz' }]", + "{'foo':'bar'}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + // A.4. Removing an Array Element + apply_patch("{'foo': [ 'bar', 'qux', 'baz' ]}", + "[{ 'op': 'remove', 'path': '/foo/1' }]", + "{'foo':['bar','baz']}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + // A.5. Replacing a Value + apply_patch("{'baz': 'qux','foo': 'bar'}", + "[{ 'op': 'replace', 'path': '/baz', 'value': 'boo' }]", + "{'foo':'bar','baz':'boo'}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + // A.5.1 #232 + apply_patch("{'a':{'c':'N','s':'F'}}", + "[{'op':'replace', 'path':'/a/s', 'value':'A'}]", + "{'a':{'c':'N','s':'A'}}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + // A.6. Moving a Value + apply_patch("{'foo': {'bar': 'baz','waldo': 'fred'},'qux': {'corge': 'grault'}}", + "[{ 'op': 'move', 'from': '/foo/waldo', 'path': '/qux/thud' }]", + "{'foo':{'bar':'baz'},'qux':{'corge':'grault','thud':'fred'}}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + // A.7. Moving an Array Element + apply_patch("{'foo': [ 'all', 'grass', 'cows', 'eat' ]}", + "[{ 'op': 'move', 'from': '/foo/1', 'path': '/foo/3' }]", + "{'foo':['all','cows','eat','grass']}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + // A.8. Testing a Value: Success + apply_patch("{'baz': 'qux','foo': [ 'a', 2, 'c' ]}", + "[" + "{ 'op': 'test', 'path': '/baz', 'value': 'qux' }," + "{ 'op': 'test', 'path': '/foo/1', 'value': 2 }" + "]", + "{'baz':'qux','foo':['a',2,'c']}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + // A.8. Testing a Value Object + apply_patch( + "{'foo':'bar','foo2':{'zaz':25, 'foo3':{'foo4':'bar4'},'foo5':'bar5'},'num1':1,'list1':['one','two',{'three':3}]}", + "[{ 'op': 'test', 'path': '/foo2', 'value': {'foo5':'bar5', 'zaz':25, 'foo3':{'foo4':'bar4'}} }]", + 0, + xstr, + &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_patch( + "{'foo':'bar','foo2':{'zaz':25, 'foo3':{'foo4':'bar4'},'foo5':'bar5'},'num1':1,'list1':['one','two',{'three':3}]}", + "[{ 'op': 'test', 'path': '/foo2', 'value': {'foo5':'bar5', 'zaz':25, 'foo3':{'foo41':'bar4'}} }]", + 0, + xstr, + &rc); + CU_ASSERT_EQUAL_FATAL(rc, JBL_ERROR_PATCH_TEST_FAILED); + iwxstr_clear(xstr); + + apply_patch( + "{'foo':'bar','foo2':{'zaz':25, 'foo3':{'foo4':'bar4'},'foo5':'bar5'},'num1':1,'list1':['one','two',{'three':3}]}", + "[{ 'op': 'test', 'path': '/', 'value': {'num1':1, 'foo2':{'foo3':{'foo4':'bar4'}, 'zaz':25, 'foo5':'bar5'},'list1':['one','two',{'three':3}],'foo':'bar'} }]", + 0, + xstr, + &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_patch( + "{'foo':'bar','foo2':{'zaz':25, 'foo3':{'foo4':'bar4'},'foo5':'bar5'},'num1':1,'list1':['one','two',{'three':3}]}", + "[{ 'op': 'test', 'path': '/list1', 'value':['one','two',{'three':3}] }]", + 0, + xstr, + &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_patch( + "{'foo':'bar','foo2':{'zaz':25, 'foo3':{'foo4':'bar4'},'foo5':'bar5'},'num1':1,'list1':['one','two',{'three':3}]}", + "[{ 'op': 'test', 'path': '/list1', 'value':['two','one',{'three':3}] }]", + 0, + xstr, + &rc); + CU_ASSERT_EQUAL_FATAL(rc, JBL_ERROR_PATCH_TEST_FAILED); + iwxstr_clear(xstr); + + // A.9. Testing a Value: Error + apply_patch("{ 'baz': 'qux'}", + "[{ 'op': 'test', 'path': '/baz', 'value': 'bar' }]", + 0, xstr, &rc); + CU_ASSERT_EQUAL(rc, JBL_ERROR_PATCH_TEST_FAILED); + iwxstr_clear(xstr); + + // A.10. Adding a nested Member Object + apply_patch("{'foo': 'bar'}", + "[{ 'op': 'add', 'path': '/child', 'value': { 'grandchild': { } } }]", + "{'foo':'bar','child':{'grandchild':{}}}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + // A.11. Ignoring Unrecognized Elements + apply_patch("{'foo': 'bar'}", + "[{ 'op': 'add', 'path': '/baz', 'value': 'qux', 'xyz': 123 }]", + "{'foo':'bar','baz':'qux'}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + // A.12. Adding to a Non-existent Target + apply_patch("{'foo': 'bar'}", + "[{ 'op': 'add', 'path': '/baz/bat', 'value': 'qux' }]", + 0, xstr, &rc); + CU_ASSERT_EQUAL(rc, JBL_ERROR_PATCH_TARGET_INVALID); + iwxstr_clear(xstr); + + // A.14. ~ Escape Ordering + apply_patch("{'/': 9,'~1': 10}", + "[{'op': 'test', 'path': '/~01', 'value': 10}]", + "{'/':9,'~1':10}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + // A.15. Comparing Strings and Numbers + apply_patch("{'/': 9,'~1': 10}", + "[{'op': 'test', 'path': '/~01', 'value': '10'}]", + "{'/':9,'~1':10}", xstr, &rc); + CU_ASSERT_EQUAL(rc, JBL_ERROR_PATCH_TEST_FAILED); + iwxstr_clear(xstr); + + // A.16. Adding an Array Value + apply_patch("{'foo': ['bar']}", + "[{'op': 'add', 'path': '/foo/-', 'value': ['abc', 'def'] }]", + "{'foo':['bar',['abc','def']]}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + // Apply non standard `increment` patch + apply_patch("{'foo': 1}", + "[{'op': 'increment', 'path': '/foo', 'value': 2}]", + "{'foo':3}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + // Apply non standard `swap` patch + apply_patch("{'foo': ['bar'], 'baz': {'gaz': 11}}", + "[{'op': 'swap', 'from': '/foo/0', 'path': '/baz/gaz'}]", + "{'foo':[11],'baz':{'gaz':'bar'}}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_patch("{'foo': ['bar'], 'baz': {'gaz': 11}}", + "[{'op': 'swap', 'from': '/foo/0', 'path': '/baz/zaz'}]", + "{'foo':[],'baz':{'gaz':11,'zaz':'bar'}}", xstr, &rc); + + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_patch("{'foo': 1}", + "[{'op': 'increment', 'path': '/foo', 'value': true}]", + "{'foo':3}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, JBL_ERROR_PATCH_INVALID_VALUE); + iwxstr_clear(xstr); + + // Apply non standard add_create patch + apply_patch("{'foo': {'bar': 1}}", + "[{'op': 'add_create', 'path': '/foo/zaz/gaz', 'value': 22}]", + "{'foo':{'bar':1,'zaz':{'gaz':22}}}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_patch("{'foo': {'bar': 1}}", + "[{'op': 'add_create', 'path': '/foo/bar/gaz', 'value': 22}]", + "{}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, JBL_ERROR_PATCH_TARGET_INVALID); + iwxstr_clear(xstr); + + apply_patch("{'foo': {'bar': 1}}", + "[{'op': 'add_create', 'path': '/zaz/gaz', 'value': [1,2,3]}]", + "{'foo':{'bar':1},'zaz':{'gaz':[1,2,3]}}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + iwxstr_destroy(xstr); +} + +void jbl_test1_7() { + iwrc rc; + IWXSTR *xstr = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(xstr); + + // #233 + apply_merge_patch("{'n':'nv'}", + "{'a':{'c':'v','d':'k'}}", + "{'n':'nv','a':{'c':'v','d':'k'}}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_merge_patch("{'a':'b'}", + "{'a':'c'}", + "{'a':'c'}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + + apply_merge_patch("{'a':'b'}", + "{'b':'c'}", + "{'a':'b','b':'c'}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_merge_patch("{'a':'b'}", + "{'a':null}", + "{}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_merge_patch("{'a':'b','b':'c'}", + "{'a':null}", + "{'b':'c'}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_merge_patch("{'a':['b']}", + "{'a':'c'}", + "{'a':'c'}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_merge_patch("{'a':'c'}", + "{'a':['b']}", + "{'a':['b']}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_merge_patch("{'a':{'b':'c'}}", + "{'a':{'b':'d','c':null}}", + "{'a':{'b':'d'}}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_merge_patch("{'a':[{'b':'c'}]}", + "{'a':[1]}", + "{'a':[1]}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_merge_patch("['a','b']", + "['c','d']", + "['c','d']", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_merge_patch("{'a':'b'}", + "['c']", + "['c']", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_merge_patch("{'e':null}", + "{'a':1}", + "{'e':null,'a':1}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_merge_patch("[1,2]", + "{'a':'b','c':null}", + "{'a':'b'}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + apply_merge_patch("{}", + "{'a':{'bb':{'ccc':null}}}", + "{'a':{'bb':{}}}", xstr, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + + + iwxstr_destroy(xstr); +} + +void jbl_test1_8() { + JBL jbl, nested, at; + iwrc rc = jbl_create_empty_object(&jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbl_create_empty_object(&nested); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbl_set_int64(nested, "nnum", 2233); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbl_set_int64(jbl, "mynum", 13223); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbl_set_string(jbl, "foo", "bar"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbl_set_nested(jbl, "nested", nested); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbl_at(jbl, "/mynum", &at); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL_FATAL(at); + CU_ASSERT_EQUAL(jbl_get_i64(at), 13223); + jbl_destroy(&at); + + rc = jbl_at(jbl, "/foo", &at); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL_FATAL(at); + CU_ASSERT_STRING_EQUAL(jbl_get_str(at), "bar"); + jbl_destroy(&at); + + rc = jbl_at(jbl, "/nested/nnum", &at); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL_FATAL(at); + CU_ASSERT_EQUAL(jbl_get_i64(at), 2233); + jbl_destroy(&at); + + jbl_destroy(&jbl); + jbl_destroy(&nested); +} + +void jbl_test1_9(void) { + IWPOOL *pool = iwpool_create(512); + IWPOOL *cpool = iwpool_create(512); + CU_ASSERT_PTR_NOT_NULL_FATAL(pool); + CU_ASSERT_PTR_NOT_NULL_FATAL(cpool); + const char *data = "{\"foo\": \"b\\\"ar\", \"num1\":1223," + "\"n\\\"um2\":10.1226222, " + "\"list\":[3,2.1,1,\"one\" \"two\", " + "{}, {\"z\":false, \"arr\":[9,8], \"t\":true}]}"; + + JBL_NODE n, cn; + iwrc rc = jbn_from_json(data, &n, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbn_clone(n, &cn, cpool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + IWXSTR *xstr = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(xstr); + + iwpool_destroy(pool); + + rc = jbn_as_json(cn, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + CU_ASSERT_STRING_EQUAL(iwxstr_ptr( + xstr), + "{\"foo\":\"b\\\"ar\",\"num1\":1223,\"n\\\"um2\":10.1226222,\"list\":[3,2.1,1,\"one\",\"two\",{},{\"z\":false,\"arr\":[9,8],\"t\":true}]}" + ); + + iwpool_destroy(cpool); + iwxstr_destroy(xstr); +} + +void jbl_test1_10(void) { + IWPOOL *pool = iwpool_create(512); + IWPOOL *tpool = iwpool_create(512); + IWXSTR *xstr = iwxstr_new(); + + const char *src_data = "{\"foo\": \"b\\\"ar\", \"num1\":1223," + "\"n\\\"um2\":10.1226222, " + "\"list\":[3,2.1,1,\"one\" \"two\", " + "{}, {\"z\":false, \"arr\":[9,8], \"t\":true}]}"; + const char *tgt_data = "{\"test\":{\"nested1\":22}}"; + JBL_NODE n1, n2; + iwrc rc = jbn_from_json(src_data, &n1, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbn_from_json(tgt_data, &n2, tpool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbn_copy_path(n1, "/list/6/arr", n2, "/test/nested1", false, false, tpool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbn_copy_path(n1, "/list/6/t", n2, "/test/t2", false, false, tpool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbn_copy_path(n1, "/foo", n2, "/bar", false, false, tpool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + iwpool_destroy(pool); + + rc = jbn_as_json(n2, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), + "{\"test\":{\"nested1\":[9,8],\"t2\":true},\"bar\":\"b\\\"ar\"}"); + + iwpool_destroy(tpool); + iwxstr_destroy(xstr); +} + +void jbl_test1_11(void) { + IWPOOL *pool = iwpool_create(512); + IWXSTR *xstr = iwxstr_new(); + + const char *src_data = "{\"foo\": \"b\\\"ar\", \"num1\":1223," + "\"n\\\"um2\":10.1226222, " + "\"list\":[3,2.1,1,\"one\" \"two\", " + "{}, {\"z\":false, \"arr\":[9,8], \"t\":true}]}"; + const char *tgt_data = "{\"test\":{\"nested1\":22}, \"list\":[0,99]}"; + + JBL_NODE n1, n2; + iwrc rc = jbn_from_json(src_data, &n1, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbn_from_json(tgt_data, &n2, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + const char *paths[] = { "/foo", "/list/1", 0 }; + rc = jbn_copy_paths(n1, n2, paths, false, false, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbn_as_json(n2, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), + "{\"test\":{\"nested1\":22},\"list\":[0,2.1],\"foo\":\"b\\\"ar\"}"); + + iwpool_destroy(pool); + iwxstr_destroy(xstr); +} + +void jbl_test1_12(void) { +} + +int main() { + CU_pSuite pSuite = NULL; + if (CUE_SUCCESS != CU_initialize_registry()) { + return CU_get_error(); + } + pSuite = CU_add_suite("jbl_test1", init_suite, clean_suite); + if (NULL == pSuite) { + CU_cleanup_registry(); + return CU_get_error(); + } + if ( (NULL == CU_add_test(pSuite, "jbl_test1_1", jbl_test1_1)) + || (NULL == CU_add_test(pSuite, "jbl_test1_2", jbl_test1_2)) + || (NULL == CU_add_test(pSuite, "jbl_test1_3", jbl_test1_3)) + || (NULL == CU_add_test(pSuite, "jbl_test1_4", jbl_test1_4)) + || (NULL == CU_add_test(pSuite, "jbl_test1_5", jbl_test1_5)) + || (NULL == CU_add_test(pSuite, "jbl_test1_6", jbl_test1_6)) + || (NULL == CU_add_test(pSuite, "jbl_test1_7", jbl_test1_7)) + || (NULL == CU_add_test(pSuite, "jbl_test1_8", jbl_test1_8)) + || (NULL == CU_add_test(pSuite, "jbl_test1_9", jbl_test1_9)) + || (NULL == CU_add_test(pSuite, "jbl_test1_10", jbl_test1_10)) + || (NULL == CU_add_test(pSuite, "jbl_test1_11", jbl_test1_11))) { + CU_cleanup_registry(); + return CU_get_error(); + } + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + int ret = CU_get_error() || CU_get_number_of_failures(); + CU_cleanup_registry(); + return ret; +} diff --git a/src/jbl/tests/jbl_test_binn1.c b/src/jbl/tests/jbl_test_binn1.c new file mode 100644 index 0000000..bd0a182 --- /dev/null +++ b/src/jbl/tests/jbl_test_binn1.c @@ -0,0 +1,1844 @@ +#include "ejdb2.h" +#include "jbl.h" +#include "jbl_internal.h" +#include + +#define INT64_FORMAT PRId64 +#define UINT64_FORMAT PRIu64 +#define INT64_HEX_FORMAT PRIx64 + +typedef unsigned short int u16; +typedef unsigned int u32; +typedef unsigned long long int u64; + +int init_suite(void) { + int rc = ejdb_init(); + return rc; +} + +int clean_suite(void) { + return 0; +} + +static int CalcAllocation(int needed_size, int alloc_size) { + int calc_size; + calc_size = alloc_size; + while (calc_size < needed_size) { + calc_size <<= 1; // same as *= 2 + //calc_size += CHUNK_SIZE; -- this is slower than the above line, because there are more reallocations + } + return calc_size; +} + +static BOOL CheckAllocation(binn *item, int add_size) { + int alloc_size; + void *ptr; + if (item->used_size + add_size > item->alloc_size) { + if (item->pre_allocated) { + return FALSE; + } + alloc_size = CalcAllocation(item->used_size + add_size, item->alloc_size); + ptr = realloc(item->pbuf, alloc_size); + if (ptr == NULL) { + return FALSE; + } + item->pbuf = ptr; + item->alloc_size = alloc_size; + } + return TRUE; +} + +#define BINN_MAGIC 0x1F22B11F + +#define MAX_BINN_HEADER 9 // [1:type][4:size][4:count] +#define MIN_BINN_SIZE 3 // [1:type][1:size][1:count] +#define CHUNK_SIZE 256 // 1024 + +extern void*(*malloc_fn)(int len); +extern void* (*realloc_fn)(void *ptr, int len); +extern void (*free_fn)(void *ptr); + +/*************************************************************************************/ + +typedef unsigned short int u16; +typedef unsigned int u32; +typedef unsigned long long int u64; + +/***************************************************************************/ + +void *memdup(void *src, int size) { + void *dest; + if ((src == NULL) || (size <= 0)) { + return NULL; + } + dest = malloc(size); + if (dest == NULL) { + return NULL; + } + memcpy(dest, src, size); + return dest; +} + +/***************************************************************************/ + +char *i64toa(int64 value, char *buf, int radix) { +#ifdef _MSC_VER + return _i64toa(value, buf, radix); +#else + switch (radix) { + case 10: + snprintf(buf, 64, "%" INT64_FORMAT, value); + break; + case 16: + snprintf(buf, 64, "%" INT64_HEX_FORMAT, value); + break; + default: + buf[0] = 0; + } + return buf; +#endif +} + +/*************************************************************************************/ + +void pass_int64(int64 a) { + + CU_ASSERT(a == 9223372036854775807); + CU_ASSERT(a > 9223372036854775806); +} + +int64 return_int64() { + + return 9223372036854775807; +} + +int64 return_passed_int64(int64 a) { + + return a; +} + +/*************************************************************************************/ + +void test_int64() { + int64 i64; + //uint64 b; + //long long int b; -- did not work! + char buf[64]; + + printf("testing int64... "); + + pass_int64(9223372036854775807); + + i64 = return_int64(); + CU_ASSERT(i64 == 9223372036854775807); + + /* do not worked! + b = 9223372036854775807; + printf("value of b1=%" G_GINT64_FORMAT "\n", b); + snprintf(64, buf, "%" G_GINT64_FORMAT, b); + printf(" value of b2=%s\n", buf); + + ltoa(i64, buf, 10); + printf(" value of i64=%s\n", buf); + */ + + i64toa(i64, buf, 10); + //printf(" value of i64=%s\n", buf); + CU_ASSERT(strcmp(buf, "9223372036854775807") == 0); + + i64 = return_passed_int64(-987654321987654321); + CU_ASSERT(i64 == -987654321987654321); + + //snprintf(64, buf, "%" G_GINT64_FORMAT, i64); + i64toa(i64, buf, 10); + CU_ASSERT(strcmp(buf, "-987654321987654321") == 0); + + printf("OK\n"); +} + +/*************************************************************************************/ + +//! this code may not work on processors that does not use the default float standard +// original name: AlmostEqual2sComplement +BOOL AlmostEqualFloats(float A, float B, int maxUlps) { + int aInt, bInt, intDiff; + // Make sure maxUlps is non-negative and small enough that the + // default NAN won't compare as equal to anything. + CU_ASSERT(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); + aInt = *(int*) &A; + bInt = *(int*) &B; + // Make aInt lexicographically ordered as a twos-complement int + if (aInt < 0) { + aInt = 0x80000000 - aInt; + } + if (bInt < 0) { + bInt = 0x80000000 - bInt; + } + intDiff = abs(aInt - bInt); + if (intDiff <= maxUlps) { + return TRUE; + } + return FALSE; +} + +/*************************************************************************************/ + +#define VERYSMALL (1.0E-150) +#define EPSILON (1.0E-8) + +#ifndef max +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef min +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +BOOL AlmostEqualDoubles(double a, double b) { + double absDiff, maxAbs, absA, absB; + + absDiff = fabs(a - b); + if (absDiff < VERYSMALL) { + return TRUE; + } + + absA = fabs(a); + absB = fabs(b); + maxAbs = max(absA, absB); + if ((absDiff / maxAbs) < EPSILON) { + return TRUE; + } + printf("a=%g b=%g\n", a, b); + return FALSE; +} + +/*************************************************************************************/ + +void test_floating_point_numbers() { + char buf[256]; + float f1; + double d1; + + printf("testing floating point... "); + + f1 = 1.25; + CU_ASSERT(f1 == 1.25); + d1 = 1.25; + CU_ASSERT(d1 == 1.25); + + d1 = 0; + d1 = f1; + CU_ASSERT(d1 == 1.25); + f1 = 0; + f1 = d1; + CU_ASSERT(f1 == 1.25); + + d1 = 1.234; + CU_ASSERT(AlmostEqualDoubles(d1, 1.234) == TRUE); + f1 = d1; + CU_ASSERT(AlmostEqualFloats(f1, 1.234, 2) == TRUE); + + d1 = 1.2345; + CU_ASSERT(AlmostEqualDoubles(d1, 1.2345) == TRUE); + f1 = d1; + CU_ASSERT(AlmostEqualFloats(f1, 1.2345, 2) == TRUE); + + + // from string to number, and back to string + + d1 = atof("1.234"); // converts from string to double + CU_ASSERT(AlmostEqualDoubles(d1, 1.234) == TRUE); + f1 = d1; // converts from double to float + CU_ASSERT(AlmostEqualFloats(f1, 1.234, 2) == TRUE); + + /* + sprintf(buf, "%f", d1); // from double to string + CU_ASSERT(buf[0] != 0); + CU_ASSERT(strcmp(buf, "1.234") == 0); + */ + + sprintf(buf, "%g", d1); + CU_ASSERT(buf[0] != 0); + CU_ASSERT(strcmp(buf, "1.234") == 0); + + + d1 = atof("12.34"); + CU_ASSERT(d1 == 12.34); + f1 = d1; + CU_ASSERT(AlmostEqualFloats(f1, 12.34, 2) == TRUE); + + /* + sprintf(buf, "%f", d1); // from double to string + CU_ASSERT(buf[0] != 0); + CU_ASSERT(strcmp(buf, "12.34") == 0); + */ + + sprintf(buf, "%g", d1); + CU_ASSERT(buf[0] != 0); + CU_ASSERT(strcmp(buf, "12.34") == 0); + + + d1 = atof("1.234e25"); + CU_ASSERT(AlmostEqualDoubles(d1, 1.234e25) == TRUE); + f1 = d1; + CU_ASSERT(AlmostEqualFloats(f1, 1.234e25, 2) == TRUE); + + sprintf(buf, "%g", d1); + CU_ASSERT(buf[0] != 0); + //printf("\nbuf=%s\n", buf); + //CU_ASSERT(strcmp(buf, "1.234e+025") == 0); + + + printf("OK\n"); +} + +/*************************************************************************************/ + +void test1() { + static const int fix_size = 512; + int i = 8768787, blobsize; + char *ptr, *p2, *ptr2; + binn *obj1, *list, *map, *obj; //, *list2=INVALID_BINN, *map2=INVALID_BINN, *obj2=INVALID_BINN; + binn value; + // test values + char vbyte, *pblob; + signed short vint16; + unsigned short vuint16; + signed int vint32; + unsigned int vuint32; + signed long long int vint64; + unsigned long long int vuint64; + + printf("testing binn 1... "); + + // CalcAllocation and CheckAllocation ------------------------------------------------- + + CU_ASSERT(CalcAllocation(512, 512) == 512); + CU_ASSERT(CalcAllocation(510, 512) == 512); + CU_ASSERT(CalcAllocation(1, 512) == 512); + CU_ASSERT(CalcAllocation(0, 512) == 512); + + CU_ASSERT(CalcAllocation(513, 512) == 1024); + CU_ASSERT(CalcAllocation(512 + CHUNK_SIZE, 512) == 1024); + CU_ASSERT(CalcAllocation(1025, 512) == 2048); + CU_ASSERT(CalcAllocation(1025, 1024) == 2048); + CU_ASSERT(CalcAllocation(2100, 1024) == 4096); + + //CU_ASSERT(CheckAllocation(xxx) == xxx); + + + // binn_new() ---------------------------------------------------------------------- + + // invalid create calls + CU_ASSERT(binn_new(-1, 0, NULL) == INVALID_BINN); + CU_ASSERT(binn_new(0, 0, NULL) == INVALID_BINN); + CU_ASSERT(binn_new(5, 0, NULL) == INVALID_BINN); + CU_ASSERT(binn_new(BINN_MAP, -1, NULL) == INVALID_BINN); + ptr = (char*) &obj1; // create a valid pointer + CU_ASSERT(binn_new(BINN_MAP, -1, ptr) == INVALID_BINN); + CU_ASSERT(binn_new(BINN_MAP, MIN_BINN_SIZE - 1, ptr) == INVALID_BINN); + + // first valid create call + obj1 = binn_new(BINN_LIST, 0, NULL); + CU_ASSERT(obj1 != INVALID_BINN); + + CU_ASSERT(obj1->header == BINN_MAGIC); + CU_ASSERT(obj1->type == BINN_LIST); + CU_ASSERT(obj1->count == 0); + CU_ASSERT(obj1->pbuf != NULL); + CU_ASSERT(obj1->alloc_size > MAX_BINN_HEADER); + CU_ASSERT(obj1->used_size == MAX_BINN_HEADER); + CU_ASSERT(obj1->pre_allocated == FALSE); + + binn_free(obj1); + + + // valid create call + list = binn_new(BINN_LIST, 0, NULL); + CU_ASSERT(list != INVALID_BINN); + + // valid create call + map = binn_new(BINN_MAP, 0, NULL); + CU_ASSERT(map != INVALID_BINN); + + // valid create call + obj = binn_new(BINN_OBJECT, 0, NULL); + CU_ASSERT(obj != INVALID_BINN); + + CU_ASSERT(list->header == BINN_MAGIC); + CU_ASSERT(list->type == BINN_LIST); + CU_ASSERT(list->count == 0); + CU_ASSERT(list->pbuf != NULL); + CU_ASSERT(list->alloc_size > MAX_BINN_HEADER); + CU_ASSERT(list->used_size == MAX_BINN_HEADER); + CU_ASSERT(list->pre_allocated == FALSE); + + CU_ASSERT(map->header == BINN_MAGIC); + CU_ASSERT(map->type == BINN_MAP); + CU_ASSERT(map->count == 0); + CU_ASSERT(map->pbuf != NULL); + CU_ASSERT(map->alloc_size > MAX_BINN_HEADER); + CU_ASSERT(map->used_size == MAX_BINN_HEADER); + CU_ASSERT(map->pre_allocated == FALSE); + + CU_ASSERT(obj->header == BINN_MAGIC); + CU_ASSERT(obj->type == BINN_OBJECT); + CU_ASSERT(obj->count == 0); + CU_ASSERT(obj->pbuf != NULL); + CU_ASSERT(obj->alloc_size > MAX_BINN_HEADER); + CU_ASSERT(obj->used_size == MAX_BINN_HEADER); + CU_ASSERT(obj->pre_allocated == FALSE); + + + // test create with pre-allocated buffer ---------------------------------------------- + + ptr = malloc(fix_size); + CU_ASSERT(ptr != NULL); + + obj1 = binn_new(BINN_OBJECT, fix_size, ptr); + CU_ASSERT(obj1 != INVALID_BINN); + + CU_ASSERT(obj1->header == BINN_MAGIC); + CU_ASSERT(obj1->type == BINN_OBJECT); + CU_ASSERT(obj1->count == 0); + CU_ASSERT(obj1->pbuf != NULL); + CU_ASSERT(obj1->alloc_size == fix_size); + CU_ASSERT(obj1->used_size == MAX_BINN_HEADER); + CU_ASSERT(obj1->pre_allocated == TRUE); + + + // add values - invalid --------------------------------------------------------------- + + CU_ASSERT(binn_map_set(list, 55001, BINN_INT32, &i, 0) == FALSE); + CU_ASSERT(binn_object_set(list, "test", BINN_INT32, &i, 0) == FALSE); + + CU_ASSERT(binn_list_add(map, BINN_INT32, &i, 0) == FALSE); + CU_ASSERT(binn_object_set(map, "test", BINN_INT32, &i, 0) == FALSE); + + CU_ASSERT(binn_list_add(obj, BINN_INT32, &i, 0) == FALSE); + CU_ASSERT(binn_map_set(obj, 55001, BINN_INT32, &i, 0) == FALSE); + + // invalid type + CU_ASSERT(binn_list_add(list, -1, &i, 0) == FALSE); + CU_ASSERT(binn_list_add(list, 0x1FFFF, &i, 0) == FALSE); + CU_ASSERT(binn_map_set(map, 5501, -1, &i, 0) == FALSE); + CU_ASSERT(binn_map_set(map, 5501, 0x1FFFF, &i, 0) == FALSE); + CU_ASSERT(binn_object_set(obj, "test", -1, &i, 0) == FALSE); + CU_ASSERT(binn_object_set(obj, "test", 0x1FFFF, &i, 0) == FALSE); + + // null pointers + CU_ASSERT(binn_list_add(list, BINN_INT8, NULL, 0) == FALSE); + CU_ASSERT(binn_list_add(list, BINN_INT16, NULL, 0) == FALSE); + CU_ASSERT(binn_list_add(list, BINN_INT32, NULL, 0) == FALSE); + CU_ASSERT(binn_list_add(list, BINN_INT64, NULL, 0) == FALSE); + //CU_ASSERT(binn_list_add(list, BINN_STRING, NULL, 0) == TRUE); //* + CU_ASSERT(binn_map_set(map, 5501, BINN_INT8, NULL, 0) == FALSE); + CU_ASSERT(binn_map_set(map, 5501, BINN_INT16, NULL, 0) == FALSE); + CU_ASSERT(binn_map_set(map, 5501, BINN_INT32, NULL, 0) == FALSE); + CU_ASSERT(binn_map_set(map, 5501, BINN_INT64, NULL, 0) == FALSE); + //CU_ASSERT(binn_map_set(map, 5501, BINN_STRING, NULL, 0) == TRUE); //* + CU_ASSERT(binn_object_set(obj, "test", BINN_INT8, NULL, 0) == FALSE); + CU_ASSERT(binn_object_set(obj, "test", BINN_INT16, NULL, 0) == FALSE); + CU_ASSERT(binn_object_set(obj, "test", BINN_INT32, NULL, 0) == FALSE); + CU_ASSERT(binn_object_set(obj, "test", BINN_INT64, NULL, 0) == FALSE); + //CU_ASSERT(binn_object_set(obj, "test", BINN_STRING, NULL, 0) == TRUE); //* + + // blobs with null pointers + CU_ASSERT(binn_list_add(list, BINN_BLOB, NULL, -1) == FALSE); + CU_ASSERT(binn_list_add(list, BINN_BLOB, NULL, 10) == FALSE); + CU_ASSERT(binn_map_set(map, 5501, BINN_BLOB, NULL, -1) == FALSE); + CU_ASSERT(binn_map_set(map, 5501, BINN_BLOB, NULL, 10) == FALSE); + CU_ASSERT(binn_object_set(obj, "test", BINN_BLOB, NULL, -1) == FALSE); + CU_ASSERT(binn_object_set(obj, "test", BINN_BLOB, NULL, 10) == FALSE); + + // blobs with negative values + CU_ASSERT(binn_list_add(list, BINN_BLOB, &i, -1) == FALSE); + CU_ASSERT(binn_list_add(list, BINN_BLOB, &i, -15) == FALSE); + CU_ASSERT(binn_map_set(map, 5501, BINN_BLOB, &i, -1) == FALSE); + CU_ASSERT(binn_map_set(map, 5501, BINN_BLOB, &i, -15) == FALSE); + CU_ASSERT(binn_object_set(obj, "test", BINN_BLOB, &i, -1) == FALSE); + CU_ASSERT(binn_object_set(obj, "test", BINN_BLOB, &i, -15) == FALSE); + + + // read values - invalid 1 - empty binns ------------------------------------------- + + ptr2 = binn_ptr(list); + CU_ASSERT(ptr2 != NULL); + CU_ASSERT(binn_list_get_value(ptr2, 0, &value) == FALSE); + CU_ASSERT(binn_list_get_value(ptr2, 1, &value) == FALSE); + CU_ASSERT(binn_list_get_value(ptr2, 2, &value) == FALSE); + CU_ASSERT(binn_list_get_value(ptr2, -1, &value) == FALSE); + + ptr2 = binn_ptr(map); + CU_ASSERT(ptr2 != NULL); + CU_ASSERT(binn_list_get_value(ptr2, 0, &value) == FALSE); + CU_ASSERT(binn_list_get_value(ptr2, 1, &value) == FALSE); + CU_ASSERT(binn_list_get_value(ptr2, 2, &value) == FALSE); + CU_ASSERT(binn_list_get_value(ptr2, -1, &value) == FALSE); + + + ptr2 = binn_ptr(obj); + CU_ASSERT(ptr2 != NULL); + CU_ASSERT(binn_list_get_value(ptr2, 0, &value) == FALSE); + CU_ASSERT(binn_list_get_value(ptr2, 1, &value) == FALSE); + CU_ASSERT(binn_list_get_value(ptr2, 2, &value) == FALSE); + CU_ASSERT(binn_list_get_value(ptr2, -1, &value) == FALSE); + + // add values - valid ----------------------------------------------------------------- + + CU_ASSERT(binn_list_add(list, BINN_INT32, &i, 0) == TRUE); + CU_ASSERT(binn_map_set(map, 5501, BINN_INT32, &i, 0) == TRUE); + CU_ASSERT(binn_map_set(map, 5501, BINN_INT32, &i, 0) == FALSE); // with the same ID + CU_ASSERT(binn_object_set(obj, "test", BINN_INT32, &i, 0) == TRUE); + CU_ASSERT(binn_object_set(obj, "test", BINN_INT32, &i, 0) == FALSE); // with the same name + + vbyte = (char) 255; + vint16 = -32000; + vuint16 = 65000; + vint32 = -65000000; + vuint32 = 65000000; + vint64 = -6500000000000000; + vuint64 = 6500000000000000; + blobsize = 150; + pblob = malloc(blobsize); + CU_ASSERT(pblob != NULL); + memset(pblob, 55, blobsize); + + CU_ASSERT(binn_list_add(list, BINN_NULL, 0, 0) == TRUE); // second + CU_ASSERT(binn_list_add(list, BINN_UINT8, &vbyte, 0) == TRUE); // third + CU_ASSERT(binn_list_add(list, BINN_INT16, &vint16, 0) == TRUE); // fourth + CU_ASSERT(binn_list_add(list, BINN_UINT16, &vuint16, 0) == TRUE); // fifth + CU_ASSERT(binn_list_add(list, BINN_INT32, &vint32, 0) == TRUE); // 6th + CU_ASSERT(binn_list_add(list, BINN_UINT32, &vuint32, 0) == TRUE); // 7th + CU_ASSERT(binn_list_add(list, BINN_INT64, &vint64, 0) == TRUE); // 8th + CU_ASSERT(binn_list_add(list, BINN_UINT64, &vuint64, 0) == TRUE); // 9th + CU_ASSERT(binn_list_add(list, BINN_STRING, "this is the string", 0) == TRUE); // 10th + CU_ASSERT(binn_list_add(list, BINN_BLOB, pblob, blobsize) == TRUE); // 11th + + CU_ASSERT(binn_map_set(map, 99000, BINN_NULL, 0, 0) == TRUE); // third + CU_ASSERT(binn_map_set(map, 99001, BINN_UINT8, &vbyte, 0) == TRUE); // fourth + CU_ASSERT(binn_map_set(map, 99002, BINN_INT16, &vint16, 0) == TRUE); // fifth + CU_ASSERT(binn_map_set(map, 99003, BINN_UINT16, &vuint16, 0) == TRUE); // 6th + CU_ASSERT(binn_map_set(map, 99004, BINN_INT32, &vint32, 0) == TRUE); // 7th + CU_ASSERT(binn_map_set(map, 99005, BINN_UINT32, &vuint32, 0) == TRUE); // 8th + CU_ASSERT(binn_map_set(map, 99006, BINN_INT64, &vint64, 0) == TRUE); // 9th + CU_ASSERT(binn_map_set(map, 99007, BINN_UINT64, &vuint64, 0) == TRUE); // 10th + CU_ASSERT(binn_map_set(map, 99008, BINN_STRING, "this is the string", 0) == TRUE); // 11th + CU_ASSERT(binn_map_set(map, 99009, BINN_BLOB, pblob, blobsize) == TRUE); // 12th + + CU_ASSERT(binn_object_set(obj, "key0", BINN_NULL, 0, 0) == TRUE); // third + CU_ASSERT(binn_object_set(obj, "key1", BINN_UINT8, &vbyte, 0) == TRUE); // fourth + CU_ASSERT(binn_object_set(obj, "key2", BINN_INT16, &vint16, 0) == TRUE); // fifth + CU_ASSERT(binn_object_set(obj, "key3", BINN_UINT16, &vuint16, 0) == TRUE); // 6th + CU_ASSERT(binn_object_set(obj, "key4", BINN_INT32, &vint32, 0) == TRUE); // 7th + CU_ASSERT(binn_object_set(obj, "key5", BINN_UINT32, &vuint32, 0) == TRUE); // 8th + CU_ASSERT(binn_object_set(obj, "key6", BINN_INT64, &vint64, 0) == TRUE); // 9th + CU_ASSERT(binn_object_set(obj, "key7", BINN_UINT64, &vuint64, 0) == TRUE); // 10th + CU_ASSERT(binn_object_set(obj, "key8", BINN_STRING, "this is the string", 0) == TRUE); // 11th + CU_ASSERT(binn_object_set(obj, "key9", BINN_BLOB, pblob, blobsize) == TRUE); // 12th + + // blobs with size = 0 + CU_ASSERT(binn_list_add(list, BINN_BLOB, ptr, 0) == TRUE); + CU_ASSERT(binn_list_add(list, BINN_STRING, "", 0) == TRUE); + CU_ASSERT(binn_list_add(list, BINN_STRING, "after the empty items", 0) == TRUE); + + + // add values to a fixed-size binn (pre-allocated buffer) -------------------------- + + CU_ASSERT(binn_list_add(obj1, BINN_INT32, &i, 0) == FALSE); + CU_ASSERT(binn_map_set(obj1, 55001, BINN_INT32, &i, 0) == FALSE); + + CU_ASSERT(binn_object_set(obj1, "test", BINN_UINT32, &vuint32, 0) == TRUE); + CU_ASSERT(binn_object_set(obj1, "test", BINN_UINT32, &vuint32, 0) == FALSE); // with the same name + + CU_ASSERT(binn_object_set(obj1, "key1", BINN_STRING, "this is the value", 0) == TRUE); + CU_ASSERT(binn_object_set(obj1, "key2", BINN_STRING, "the second value", 0) == TRUE); + + // create a long string buffer to make the test. the string is longer than the available space + // in the binn. + ptr2 = malloc(fix_size); + CU_ASSERT(ptr2 != NULL); + p2 = ptr2; + for (i = 0; i < fix_size - 1; i++) { + *p2 = 'A'; + p2++; + } + *p2 = '\0'; + CU_ASSERT(strlen(ptr2) == fix_size - 1); + + CU_ASSERT(binn_object_set(obj1, "v2", BINN_STRING, ptr2, + 0) == FALSE); // it fails because it uses a pre-allocated memory block + + CU_ASSERT(binn_object_set(obj, "v2", BINN_STRING, ptr2, + 0) == TRUE); // but this uses a dynamically allocated memory block, so it works with it + CU_ASSERT(binn_object_set(obj, "Key00", BINN_STRING, "after the big string", + 0) == TRUE); // and test the 'Key00' against the 'Key0' + + CU_ASSERT(binn_object_set(obj, "list", BINN_LIST, binn_ptr(list), binn_size(list)) == TRUE); + CU_ASSERT(binn_object_set(obj, "Key10", BINN_STRING, "after the list", + 0) == TRUE); // and test the 'Key10' against the 'Key1' + + + // read values - invalid 2 ------------------------------------------------------------ + + + // read keys -------------------------------------------------------------------------- + + + // binn_size - invalid and valid args -------------------------------------------- + + CU_ASSERT(binn_size(NULL) == 0); + + CU_ASSERT(binn_size(list) == list->size); + CU_ASSERT(binn_size(map) == map->size); + CU_ASSERT(binn_size(obj) == obj->size); + CU_ASSERT(binn_size(obj1) == obj1->size); + + + // destroy them all ------------------------------------------------------------------- + + binn_free(list); + binn_free(map); + binn_free(obj); + binn_free(obj1); + free(pblob); + free(ptr); + free(ptr2); + + + printf("OK\n"); +} + +/*************************************************************************************/ + +void _test2() { + binn *list = INVALID_BINN, *map = INVALID_BINN, *obj = INVALID_BINN; + binn value; + BOOL vbool; + int blobsize; + char *pblob, *pstr; + signed int vint32; + double vdouble; + + char *str_list = "test list"; + char *str_map = "test map"; + char *str_obj = "test object"; + + printf("testing binn 2"); + + blobsize = 150; + pblob = malloc(blobsize); + CU_ASSERT(pblob != NULL); + memset(pblob, 55, blobsize); + + CU_ASSERT(list == INVALID_BINN); + CU_ASSERT(map == INVALID_BINN); + CU_ASSERT(obj == INVALID_BINN); + + // add values without creating before + + CU_ASSERT(binn_list_add_int32(list, 123) == FALSE); + CU_ASSERT(binn_map_set_int32(map, 1001, 456) == FALSE); + CU_ASSERT(binn_object_set_int32(obj, "int", 789) == FALSE); + + // create the structures + + list = binn_list(); + map = binn_map(); + obj = binn_object(); + + CU_ASSERT(list != INVALID_BINN); + CU_ASSERT(map != INVALID_BINN); + CU_ASSERT(obj != INVALID_BINN); + + // add values without creating before + + CU_ASSERT(binn_list_add_int32(list, 123) == TRUE); + CU_ASSERT(binn_map_set_int32(map, 1001, 456) == TRUE); + CU_ASSERT(binn_object_set_int32(obj, "int", 789) == TRUE); + + // check the structures + + CU_ASSERT(list->header == BINN_MAGIC); + CU_ASSERT(list->type == BINN_LIST); + CU_ASSERT(list->count == 1); + CU_ASSERT(list->pbuf != NULL); + CU_ASSERT(list->alloc_size > MAX_BINN_HEADER); + CU_ASSERT(list->used_size > MAX_BINN_HEADER); + CU_ASSERT(list->pre_allocated == FALSE); + + CU_ASSERT(map->header == BINN_MAGIC); + CU_ASSERT(map->type == BINN_MAP); + CU_ASSERT(map->count == 1); + CU_ASSERT(map->pbuf != NULL); + CU_ASSERT(map->alloc_size > MAX_BINN_HEADER); + CU_ASSERT(map->used_size > MAX_BINN_HEADER); + CU_ASSERT(map->pre_allocated == FALSE); + + CU_ASSERT(obj->header == BINN_MAGIC); + CU_ASSERT(obj->type == BINN_OBJECT); + CU_ASSERT(obj->count == 1); + CU_ASSERT(obj->pbuf != NULL); + CU_ASSERT(obj->alloc_size > MAX_BINN_HEADER); + CU_ASSERT(obj->used_size > MAX_BINN_HEADER); + CU_ASSERT(obj->pre_allocated == FALSE); + + + // continue adding values + + CU_ASSERT(binn_list_add_double(list, 1.23) == TRUE); + CU_ASSERT(binn_map_set_double(map, 1002, 4.56) == TRUE); + CU_ASSERT(binn_object_set_double(obj, "double", 7.89) == TRUE); + + CU_ASSERT(list->count == 2); + CU_ASSERT(map->count == 2); + CU_ASSERT(obj->count == 2); + + CU_ASSERT(binn_list_add_bool(list, TRUE) == TRUE); + CU_ASSERT(binn_map_set_bool(map, 1003, TRUE) == TRUE); + CU_ASSERT(binn_object_set_bool(obj, "bool", TRUE) == TRUE); + + CU_ASSERT(list->count == 3); + CU_ASSERT(map->count == 3); + CU_ASSERT(obj->count == 3); + + CU_ASSERT(binn_list_add_str(list, str_list) == TRUE); + CU_ASSERT(binn_map_set_str(map, 1004, str_map) == TRUE); + CU_ASSERT(binn_object_set_str(obj, "text", str_obj) == TRUE); + + CU_ASSERT(list->count == 4); + CU_ASSERT(map->count == 4); + CU_ASSERT(obj->count == 4); + + CU_ASSERT(binn_list_add_blob(list, pblob, blobsize) == TRUE); + CU_ASSERT(binn_map_set_blob(map, 1005, pblob, blobsize) == TRUE); + CU_ASSERT(binn_object_set_blob(obj, "blob", pblob, blobsize) == TRUE); + + CU_ASSERT(list->count == 5); + CU_ASSERT(map->count == 5); + CU_ASSERT(obj->count == 5); + + CU_ASSERT(binn_count(list) == 5); + CU_ASSERT(binn_count(map) == 5); + CU_ASSERT(binn_count(obj) == 5); + + CU_ASSERT(binn_size(list) == list->size); + CU_ASSERT(binn_size(map) == map->size); + CU_ASSERT(binn_size(obj) == obj->size); + + CU_ASSERT(binn_type(list) == BINN_LIST); + CU_ASSERT(binn_type(map) == BINN_MAP); + CU_ASSERT(binn_type(obj) == BINN_OBJECT); + + + // try to read them + + // integer + + CU_ASSERT(binn_list_get_value(list, 1, &value) == TRUE); + + CU_ASSERT(value.header == BINN_MAGIC); + CU_ASSERT(value.writable == FALSE); + CU_ASSERT(value.allocated == FALSE); + + CU_ASSERT(value.type == BINN_UINT8); + CU_ASSERT(value.ptr != &value.vuint8); // it must return a pointer to the byte in the buffer + + CU_ASSERT(value.size == 0); + CU_ASSERT(value.count == 0); + CU_ASSERT(value.vint == 123); + + memset(&value, 0, sizeof(binn)); + + CU_ASSERT(binn_map_get_value(map, 1001, &value) == TRUE); + + CU_ASSERT(value.header == BINN_MAGIC); + CU_ASSERT(value.writable == FALSE); + + CU_ASSERT(value.type == BINN_UINT16); + CU_ASSERT(value.ptr == &value.vuint16); + + CU_ASSERT(value.size == 0); + CU_ASSERT(value.count == 0); + CU_ASSERT(value.vint == 456); + + memset(&value, 0, sizeof(binn)); + + CU_ASSERT(binn_object_get_value(obj, "int", &value) == TRUE); + + CU_ASSERT(value.header == BINN_MAGIC); + CU_ASSERT(value.writable == FALSE); + + CU_ASSERT(value.type == BINN_UINT16); + CU_ASSERT(value.ptr == &value.vuint16); + + CU_ASSERT(value.size == 0); + CU_ASSERT(value.count == 0); + CU_ASSERT(value.vint == 789); + + memset(&value, 0, sizeof(binn)); + + + // double + + CU_ASSERT(binn_list_get_value(list, 2, &value) == TRUE); + + CU_ASSERT(value.header == BINN_MAGIC); + CU_ASSERT(value.writable == FALSE); + CU_ASSERT(value.type == BINN_FLOAT64); + CU_ASSERT(value.ptr == &value.vint); + CU_ASSERT(value.size == 0); + CU_ASSERT(value.count == 0); + CU_ASSERT(value.vdouble == 1.23); + + memset(&value, 0, sizeof(binn)); + + CU_ASSERT(binn_map_get_value(map, 1002, &value) == TRUE); + + CU_ASSERT(value.header == BINN_MAGIC); + CU_ASSERT(value.writable == FALSE); + CU_ASSERT(value.type == BINN_FLOAT64); + CU_ASSERT(value.ptr == &value.vint); + CU_ASSERT(value.size == 0); + CU_ASSERT(value.count == 0); + CU_ASSERT(value.vdouble == 4.56); + + memset(&value, 0, sizeof(binn)); + + CU_ASSERT(binn_object_get_value(obj, "double", &value) == TRUE); + + CU_ASSERT(value.header == BINN_MAGIC); + CU_ASSERT(value.writable == FALSE); + CU_ASSERT(value.type == BINN_FLOAT64); + CU_ASSERT(value.ptr == &value.vint); + CU_ASSERT(value.size == 0); + CU_ASSERT(value.count == 0); + CU_ASSERT(value.vdouble == 7.89); + + memset(&value, 0, sizeof(binn)); + + + // bool + + CU_ASSERT(binn_list_get_value(list, 3, &value) == TRUE); + + CU_ASSERT(value.header == BINN_MAGIC); + CU_ASSERT(value.writable == FALSE); + CU_ASSERT(value.type == BINN_BOOL); + CU_ASSERT(value.ptr == &value.vint); + CU_ASSERT(value.size == 0); + CU_ASSERT(value.count == 0); + CU_ASSERT(value.vbool == TRUE); + + memset(&value, 0, sizeof(binn)); + + CU_ASSERT(binn_map_get_value(map, 1003, &value) == TRUE); + + CU_ASSERT(value.header == BINN_MAGIC); + CU_ASSERT(value.writable == FALSE); + CU_ASSERT(value.type == BINN_BOOL); + CU_ASSERT(value.ptr == &value.vint); + CU_ASSERT(value.size == 0); + CU_ASSERT(value.count == 0); + CU_ASSERT(value.vbool == TRUE); + + CU_ASSERT(binn_object_get_value(obj, "bool", &value) == TRUE); + + CU_ASSERT(value.header == BINN_MAGIC); + CU_ASSERT(value.writable == FALSE); + CU_ASSERT(value.type == BINN_BOOL); + CU_ASSERT(value.ptr == &value.vint); + CU_ASSERT(value.size == 0); + CU_ASSERT(value.count == 0); + CU_ASSERT(value.vbool == TRUE); + + memset(&value, 0, sizeof(binn)); + + + // string + + CU_ASSERT(binn_list_get_value(list, 4, &value) == TRUE); + + CU_ASSERT(value.header == BINN_MAGIC); + CU_ASSERT(value.writable == FALSE); + CU_ASSERT(value.type == BINN_STRING); + CU_ASSERT(value.ptr != 0); + CU_ASSERT(value.size == strlen(str_list)); + CU_ASSERT(strcmp(value.ptr, str_list) == 0); + CU_ASSERT(value.count == 0); + + memset(&value, 0, sizeof(binn)); + + CU_ASSERT(binn_map_get_value(map, 1004, &value) == TRUE); + + CU_ASSERT(value.header == BINN_MAGIC); + CU_ASSERT(value.writable == FALSE); + CU_ASSERT(value.type == BINN_STRING); + CU_ASSERT(value.size == strlen(str_map)); + CU_ASSERT(strcmp(value.ptr, str_map) == 0); + CU_ASSERT(value.count == 0); + + memset(&value, 0, sizeof(binn)); + + CU_ASSERT(binn_object_get_value(obj, "text", &value) == TRUE); + + CU_ASSERT(value.header == BINN_MAGIC); + CU_ASSERT(value.writable == FALSE); + CU_ASSERT(value.type == BINN_STRING); + CU_ASSERT(value.size == strlen(str_obj)); + CU_ASSERT(strcmp(value.ptr, str_obj) == 0); + CU_ASSERT(value.count == 0); + + memset(&value, 0, sizeof(binn)); + + + // blob + + CU_ASSERT(binn_list_get_value(list, 5, &value) == TRUE); + + CU_ASSERT(value.header == BINN_MAGIC); + CU_ASSERT(value.writable == FALSE); + CU_ASSERT(value.type == BINN_BLOB); + CU_ASSERT(value.ptr != 0); + CU_ASSERT(value.size == blobsize); + CU_ASSERT(memcmp(value.ptr, pblob, blobsize) == 0); + CU_ASSERT(value.count == 0); + + memset(&value, 0, sizeof(binn)); + + CU_ASSERT(binn_map_get_value(map, 1005, &value) == TRUE); + + CU_ASSERT(value.header == BINN_MAGIC); + CU_ASSERT(value.writable == FALSE); + CU_ASSERT(value.type == BINN_BLOB); + CU_ASSERT(value.ptr != 0); + CU_ASSERT(value.size == blobsize); + CU_ASSERT(memcmp(value.ptr, pblob, blobsize) == 0); + CU_ASSERT(value.count == 0); + + memset(&value, 0, sizeof(binn)); + + CU_ASSERT(binn_object_get_value(obj, "blob", &value) == TRUE); + + CU_ASSERT(value.header == BINN_MAGIC); + CU_ASSERT(value.writable == FALSE); + CU_ASSERT(value.type == BINN_BLOB); + CU_ASSERT(value.ptr != 0); + CU_ASSERT(value.size == blobsize); + CU_ASSERT(memcmp(value.ptr, pblob, blobsize) == 0); + CU_ASSERT(value.count == 0); + + memset(&value, 0, sizeof(binn)); + + + // read with other interface + + CU_ASSERT(binn_list_get_int32(list, 1, &vint32) == TRUE); + CU_ASSERT(vint32 == 123); + + CU_ASSERT(binn_map_get_int32(map, 1001, &vint32) == TRUE); + CU_ASSERT(vint32 == 456); + + CU_ASSERT(binn_object_get_int32(obj, "int", &vint32) == TRUE); + CU_ASSERT(vint32 == 789); + + // double + + CU_ASSERT(binn_list_get_double(list, 2, &vdouble) == TRUE); + CU_ASSERT(vdouble == 1.23); + + CU_ASSERT(binn_map_get_double(map, 1002, &vdouble) == TRUE); + CU_ASSERT(vdouble == 4.56); + + CU_ASSERT(binn_object_get_double(obj, "double", &vdouble) == TRUE); + CU_ASSERT(vdouble == 7.89); + + // bool + + CU_ASSERT(binn_list_get_bool(list, 3, &vbool) == TRUE); + CU_ASSERT(vbool == TRUE); + + CU_ASSERT(binn_map_get_bool(map, 1003, &vbool) == TRUE); + CU_ASSERT(vbool == TRUE); + + CU_ASSERT(binn_object_get_bool(obj, "bool", &vbool) == TRUE); + CU_ASSERT(vbool == TRUE); + + // string + + CU_ASSERT(binn_list_get_str(list, 4, &pstr) == TRUE); + CU_ASSERT(pstr != 0); + CU_ASSERT(strcmp(pstr, str_list) == 0); + + CU_ASSERT(binn_map_get_str(map, 1004, &pstr) == TRUE); + CU_ASSERT(pstr != 0); + CU_ASSERT(strcmp(pstr, str_map) == 0); + + CU_ASSERT(binn_object_get_str(obj, "text", &pstr) == TRUE); + CU_ASSERT(pstr != 0); + CU_ASSERT(strcmp(pstr, str_obj) == 0); + + // blob + + value.ptr = 0; + value.size = 0; + CU_ASSERT(binn_list_get_blob(list, 5, &value.ptr, &value.size) == TRUE); + CU_ASSERT(value.ptr != 0); + CU_ASSERT(value.size == blobsize); + CU_ASSERT(memcmp(value.ptr, pblob, blobsize) == 0); + + value.ptr = 0; + value.size = 0; + CU_ASSERT(binn_map_get_blob(map, 1005, &value.ptr, &value.size) == TRUE); + CU_ASSERT(value.ptr != 0); + CU_ASSERT(value.size == blobsize); + CU_ASSERT(memcmp(value.ptr, pblob, blobsize) == 0); + + value.ptr = 0; + value.size = 0; + CU_ASSERT(binn_object_get_blob(obj, "blob", &value.ptr, &value.size) == TRUE); + CU_ASSERT(value.ptr != 0); + CU_ASSERT(value.size == blobsize); + CU_ASSERT(memcmp(value.ptr, pblob, blobsize) == 0); + + + // read with other interface + + CU_ASSERT(binn_list_int32(list, 1) == 123); + CU_ASSERT(binn_map_int32(map, 1001) == 456); + CU_ASSERT(binn_object_int32(obj, "int") == 789); + + // double + + CU_ASSERT(binn_list_double(list, 2) == 1.23); + CU_ASSERT(binn_map_double(map, 1002) == 4.56); + CU_ASSERT(binn_object_double(obj, "double") == 7.89); + + // bool + + CU_ASSERT(binn_list_bool(list, 3) == TRUE); + CU_ASSERT(binn_map_bool(map, 1003) == TRUE); + CU_ASSERT(binn_object_bool(obj, "bool") == TRUE); + + // string + + pstr = binn_list_str(list, 4); + CU_ASSERT(pstr != 0); + CU_ASSERT(strcmp(pstr, str_list) == 0); + + pstr = binn_map_str(map, 1004); + CU_ASSERT(pstr != 0); + CU_ASSERT(strcmp(pstr, str_map) == 0); + + pstr = binn_object_str(obj, "text"); + CU_ASSERT(pstr != 0); + CU_ASSERT(strcmp(pstr, str_obj) == 0); + + // blob + + value.ptr = binn_list_blob(list, 5, &value.size); + CU_ASSERT(value.ptr != 0); + CU_ASSERT(value.size == blobsize); + CU_ASSERT(memcmp(value.ptr, pblob, blobsize) == 0); + + value.ptr = binn_map_blob(map, 1005, &value.size); + CU_ASSERT(value.ptr != 0); + CU_ASSERT(value.size == blobsize); + CU_ASSERT(memcmp(value.ptr, pblob, blobsize) == 0); + + value.ptr = binn_object_blob(obj, "blob", &value.size); + CU_ASSERT(value.ptr != 0); + CU_ASSERT(value.size == blobsize); + CU_ASSERT(memcmp(value.ptr, pblob, blobsize) == 0); + + + binn_free(list); + binn_free(map); + binn_free(obj); + + + free(pblob); + + printf("OK\n"); +} + +void test2() { + _test2(); +} + +/*************************************************************************************/ + +void test4() { + static const int fix_size = 512; + int i, id, type, count, size, header_size, blobsize; + char *ptr, *p2, *pstr, key[256]; + binn *list, *map, *obj, *obj1; + binn value; + // test values + char vbyte, *pblob; + signed short vint16, *pint16; + unsigned short vuint16, *puint16; + signed int vint32, *pint32; + unsigned int vuint32, *puint32; + signed long long int vint64, *pint64; + unsigned long long int vuint64, *puint64; + + printf("testing binn 3... "); + + list = binn_list(); + CU_ASSERT(list != INVALID_BINN); + + map = binn_map(); + CU_ASSERT(map != INVALID_BINN); + + obj = binn_object(); + CU_ASSERT(obj != INVALID_BINN); + + CU_ASSERT(list->header == BINN_MAGIC); + CU_ASSERT(list->type == BINN_LIST); + CU_ASSERT(list->count == 0); + CU_ASSERT(list->pbuf != NULL); + CU_ASSERT(list->alloc_size > MAX_BINN_HEADER); + CU_ASSERT(list->used_size == MAX_BINN_HEADER); + CU_ASSERT(list->pre_allocated == FALSE); + + CU_ASSERT(map->header == BINN_MAGIC); + CU_ASSERT(map->type == BINN_MAP); + CU_ASSERT(map->count == 0); + CU_ASSERT(map->pbuf != NULL); + CU_ASSERT(map->alloc_size > MAX_BINN_HEADER); + CU_ASSERT(map->used_size == MAX_BINN_HEADER); + CU_ASSERT(map->pre_allocated == FALSE); + + CU_ASSERT(obj->header == BINN_MAGIC); + CU_ASSERT(obj->type == BINN_OBJECT); + CU_ASSERT(obj->count == 0); + CU_ASSERT(obj->pbuf != NULL); + CU_ASSERT(obj->alloc_size > MAX_BINN_HEADER); + CU_ASSERT(obj->used_size == MAX_BINN_HEADER); + CU_ASSERT(obj->pre_allocated == FALSE); + + + // test create with pre-allocated buffer ---------------------------------------------- + + ptr = malloc(fix_size); + CU_ASSERT(ptr != NULL); + + obj1 = binn_new(BINN_OBJECT, fix_size, ptr); + CU_ASSERT(obj1 != INVALID_BINN); + + CU_ASSERT(obj1->header == BINN_MAGIC); + CU_ASSERT(obj1->type == BINN_OBJECT); + CU_ASSERT(obj1->count == 0); + CU_ASSERT(obj1->pbuf != NULL); + CU_ASSERT(obj1->alloc_size == fix_size); + CU_ASSERT(obj1->used_size == MAX_BINN_HEADER); + CU_ASSERT(obj1->pre_allocated == TRUE); + + + // add values - invalid --------------------------------------------------------------- + + + // read values - invalid 1 - empty binns ------------------------------------------- + + ptr = binn_ptr(list); + CU_ASSERT(ptr != NULL); + CU_ASSERT(binn_list_read(ptr, 0, &type, &size) == NULL); + CU_ASSERT(binn_list_read(ptr, 1, &type, &size) == NULL); + CU_ASSERT(binn_list_read(ptr, 2, &type, &size) == NULL); + CU_ASSERT(binn_list_read(ptr, -1, &type, &size) == NULL); + + ptr = binn_ptr(map); + CU_ASSERT(ptr != NULL); + CU_ASSERT(binn_map_read(ptr, 0, &type, &size) == NULL); + CU_ASSERT(binn_map_read(ptr, 55001, &type, &size) == NULL); + CU_ASSERT(binn_map_read(ptr, -1, &type, &size) == NULL); + + ptr = binn_ptr(obj); + CU_ASSERT(ptr != NULL); + CU_ASSERT(binn_object_read(ptr, NULL, &type, &size) == NULL); + CU_ASSERT(binn_object_read(ptr, "", &type, &size) == NULL); + CU_ASSERT(binn_object_read(ptr, "test", &type, &size) == NULL); + + + // add values - valid ----------------------------------------------------------------- + + CU_ASSERT(binn_list_add(list, BINN_INT32, &i, 0) == TRUE); + CU_ASSERT(binn_map_set(map, 5501, BINN_INT32, &i, 0) == TRUE); + CU_ASSERT(binn_map_set(map, 5501, BINN_INT32, &i, 0) == FALSE); // with the same ID + CU_ASSERT(binn_object_set(obj, "test", BINN_INT32, &i, 0) == TRUE); + CU_ASSERT(binn_object_set(obj, "test", BINN_INT32, &i, 0) == FALSE); // with the same name + + vbyte = (char) 255; + vint16 = -32000; + vuint16 = 65000; + vint32 = -65000000; + vuint32 = 65000000; + vint64 = -6500000000000000; + vuint64 = 6500000000000000; + blobsize = 150; + pblob = malloc(blobsize); + CU_ASSERT(pblob != NULL); + memset(pblob, 55, blobsize); + + CU_ASSERT(binn_list_add(list, BINN_NULL, 0, 0) == TRUE); // second + CU_ASSERT(binn_list_add(list, BINN_UINT8, &vbyte, 0) == TRUE); // third + CU_ASSERT(binn_list_add(list, BINN_INT16, &vint16, 0) == TRUE); // fourth + CU_ASSERT(binn_list_add(list, BINN_UINT16, &vuint16, 0) == TRUE); // fifth + CU_ASSERT(binn_list_add(list, BINN_INT32, &vint32, 0) == TRUE); // 6th + CU_ASSERT(binn_list_add(list, BINN_UINT32, &vuint32, 0) == TRUE); // 7th + CU_ASSERT(binn_list_add(list, BINN_INT64, &vint64, 0) == TRUE); // 8th + CU_ASSERT(binn_list_add(list, BINN_UINT64, &vuint64, 0) == TRUE); // 9th + CU_ASSERT(binn_list_add(list, BINN_STRING, "this is the string", 0) == TRUE); // 10th + CU_ASSERT(binn_list_add(list, BINN_BLOB, pblob, blobsize) == TRUE); // 11th + + CU_ASSERT(binn_map_set(map, 99000, BINN_NULL, 0, 0) == TRUE); // third + CU_ASSERT(binn_map_set(map, 99001, BINN_UINT8, &vbyte, 0) == TRUE); // fourth + CU_ASSERT(binn_map_set(map, 99002, BINN_INT16, &vint16, 0) == TRUE); // fifth + CU_ASSERT(binn_map_set(map, 99003, BINN_UINT16, &vuint16, 0) == TRUE); // 6th + CU_ASSERT(binn_map_set(map, 99004, BINN_INT32, &vint32, 0) == TRUE); // 7th + CU_ASSERT(binn_map_set(map, 99005, BINN_UINT32, &vuint32, 0) == TRUE); // 8th + CU_ASSERT(binn_map_set(map, 99006, BINN_INT64, &vint64, 0) == TRUE); // 9th + CU_ASSERT(binn_map_set(map, 99007, BINN_UINT64, &vuint64, 0) == TRUE); // 10th + CU_ASSERT(binn_map_set(map, 99008, BINN_STRING, "this is the string", 0) == TRUE); // 11th + CU_ASSERT(binn_map_set(map, 99009, BINN_BLOB, pblob, blobsize) == TRUE); // 12th + + CU_ASSERT(binn_object_set(obj, "key0", BINN_NULL, 0, 0) == TRUE); // third + CU_ASSERT(binn_object_set(obj, "key1", BINN_UINT8, &vbyte, 0) == TRUE); // fourth + CU_ASSERT(binn_object_set(obj, "key2", BINN_INT16, &vint16, 0) == TRUE); // fifth + CU_ASSERT(binn_object_set(obj, "key3", BINN_UINT16, &vuint16, 0) == TRUE); // 6th + CU_ASSERT(binn_object_set(obj, "key4", BINN_INT32, &vint32, 0) == TRUE); // 7th + CU_ASSERT(binn_object_set(obj, "key5", BINN_UINT32, &vuint32, 0) == TRUE); // 8th + CU_ASSERT(binn_object_set(obj, "key6", BINN_INT64, &vint64, 0) == TRUE); // 9th + CU_ASSERT(binn_object_set(obj, "key7", BINN_UINT64, &vuint64, 0) == TRUE); // 10th + CU_ASSERT(binn_object_set(obj, "key8", BINN_STRING, "this is the string", 0) == TRUE); // 11th + CU_ASSERT(binn_object_set(obj, "key9", BINN_BLOB, pblob, blobsize) == TRUE); // 12th + + // blobs with size = 0 + CU_ASSERT(binn_list_add(list, BINN_BLOB, ptr, 0) == TRUE); + CU_ASSERT(binn_list_add(list, BINN_STRING, "", 0) == TRUE); + CU_ASSERT(binn_list_add(list, BINN_STRING, "after the empty items", 0) == TRUE); + + + // add values to a fixed-size binn (pre-allocated buffer) -------------------------- + + CU_ASSERT(binn_list_add(obj1, BINN_INT32, &i, 0) == FALSE); + CU_ASSERT(binn_map_set(obj1, 55001, BINN_INT32, &i, 0) == FALSE); + + CU_ASSERT(binn_object_set(obj1, "test", BINN_UINT32, &vuint32, 0) == TRUE); + CU_ASSERT(binn_object_set(obj1, "test", BINN_UINT32, &vuint32, 0) == FALSE); // with the same name + + CU_ASSERT(binn_object_set(obj1, "key1", BINN_STRING, "this is the value", 0) == TRUE); + CU_ASSERT(binn_object_set(obj1, "key2", BINN_STRING, "the second value", 0) == TRUE); + + // create a long string buffer to make the test. the string is longer than the available space + // in the binn. + ptr = malloc(fix_size); + CU_ASSERT(ptr != NULL); + p2 = ptr; + for (i = 0; i < fix_size - 1; i++) { + *p2 = 'A'; + p2++; + } + *p2 = '\0'; + CU_ASSERT(strlen(ptr) == fix_size - 1); + + CU_ASSERT(binn_object_set(obj1, "v2", BINN_STRING, ptr, + 0) == FALSE); // it fails because it uses a pre-allocated memory block + + CU_ASSERT(binn_object_set(obj, "v2", BINN_STRING, ptr, + 0) == TRUE); // but this uses a dynamically allocated memory block, so it works with it + CU_ASSERT(binn_object_set(obj, "Key00", BINN_STRING, "after the big string", + 0) == TRUE); // and test the 'Key00' against the 'Key0' + + free(ptr); + ptr = 0; + + CU_ASSERT(binn_object_set(obj, "list", BINN_LIST, binn_ptr(list), binn_size(list)) == TRUE); + CU_ASSERT(binn_object_set(obj, "Key10", BINN_STRING, "after the list", + 0) == TRUE); // and test the 'Key10' against the 'Key1' + + + // read values - invalid 2 ------------------------------------------------------------ + + + // read keys -------------------------------------------------------------------------- + + ptr = binn_ptr(map); + CU_ASSERT(ptr != NULL); + + CU_ASSERT(binn_map_get_pair(ptr, -1, &id, &value) == FALSE); + CU_ASSERT(binn_map_get_pair(ptr, 0, &id, &value) == FALSE); + + CU_ASSERT(binn_map_get_pair(ptr, 1, &id, &value) == TRUE); + CU_ASSERT(id == 5501); + CU_ASSERT(binn_map_get_pair(ptr, 2, &id, &value) == TRUE); + CU_ASSERT(id == 99000); + CU_ASSERT(binn_map_get_pair(ptr, 3, &id, &value) == TRUE); + CU_ASSERT(id == 99001); + CU_ASSERT(binn_map_get_pair(ptr, 10, &id, &value) == TRUE); + CU_ASSERT(id == 99008); + CU_ASSERT(binn_map_get_pair(ptr, 11, &id, &value) == TRUE); + CU_ASSERT(id == 99009); + + + ptr = binn_ptr(obj); + CU_ASSERT(ptr != NULL); + + CU_ASSERT(binn_object_get_pair(ptr, -1, key, &value) == FALSE); + CU_ASSERT(binn_object_get_pair(ptr, 0, key, &value) == FALSE); + + CU_ASSERT(binn_object_get_pair(ptr, 1, key, &value) == TRUE); + CU_ASSERT(strcmp(key, "test") == 0); + CU_ASSERT(binn_object_get_pair(ptr, 2, key, &value) == TRUE); + CU_ASSERT(strcmp(key, "key0") == 0); + CU_ASSERT(binn_object_get_pair(ptr, 3, key, &value) == TRUE); + CU_ASSERT(strcmp(key, "key1") == 0); + CU_ASSERT(binn_object_get_pair(ptr, 10, key, &value) == TRUE); + CU_ASSERT(strcmp(key, "key8") == 0); + CU_ASSERT(binn_object_get_pair(ptr, 11, key, &value) == TRUE); + CU_ASSERT(strcmp(key, "key9") == 0); + + + // read values - valid ---------------------------------------------------------------- + + ptr = binn_ptr(obj1); + CU_ASSERT(ptr != NULL); + + type = 0; + size = 0; + pstr = binn_object_read(ptr, "key1", &type, &size); + CU_ASSERT(pstr != NULL); + CU_ASSERT(type == BINN_STRING); + CU_ASSERT(size > 0); + CU_ASSERT(strcmp(pstr, "this is the value") == 0); + + type = 0; + size = 0; + pstr = binn_object_read(ptr, "key2", &type, &size); + CU_ASSERT(pstr != NULL); + CU_ASSERT(type == BINN_STRING); + CU_ASSERT(size > 0); + CU_ASSERT(strcmp(pstr, "the second value") == 0); + + type = 0; + size = 0; + pint32 = binn_object_read(ptr, "test", &type, &size); + CU_ASSERT(pint32 != NULL); + CU_ASSERT(type == BINN_UINT32); + //CU_ASSERT(size > 0); + CU_ASSERT(*pint32 == vuint32); + + + ptr = binn_ptr(list); + CU_ASSERT(ptr != NULL); + + type = 0; + size = 0; + pstr = binn_list_read(ptr, 2, &type, &size); + CU_ASSERT(pstr != NULL); + CU_ASSERT(type == BINN_NULL); + //CU_ASSERT(size > 0); + //CU_ASSERT(strcmp(pstr, "this is the value") == 0); + + type = 0; + size = 0; + p2 = binn_list_read(ptr, 3, &type, &size); + CU_ASSERT(p2 != NULL); + CU_ASSERT(type == BINN_UINT8); + CU_ASSERT(*p2 == vbyte); + + type = 0; + size = 0; + pint16 = binn_list_read(ptr, 4, &type, &size); + CU_ASSERT(pint16 != NULL); + CU_ASSERT(type == BINN_INT16); + CU_ASSERT(*pint16 == vint16); + + type = 0; + size = 0; + puint16 = binn_list_read(ptr, 5, &type, &size); + CU_ASSERT(puint16 != NULL); + CU_ASSERT(type == BINN_UINT16); + CU_ASSERT(*puint16 == vuint16); + + type = 0; + size = 0; + pint32 = binn_list_read(ptr, 6, &type, &size); + CU_ASSERT(pint32 != NULL); + CU_ASSERT(type == BINN_INT32); + CU_ASSERT(*pint32 == vint32); + // in the second time the value must be the same... + type = 0; + size = 0; + pint32 = binn_list_read(ptr, 6, &type, &size); + CU_ASSERT(pint32 != NULL); + CU_ASSERT(type == BINN_INT32); + CU_ASSERT(*pint32 == vint32); + + type = 0; + size = 0; + puint32 = binn_list_read(ptr, 7, &type, &size); + CU_ASSERT(puint32 != NULL); + CU_ASSERT(type == BINN_UINT32); + CU_ASSERT(*puint32 == vuint32); + + type = 0; + size = 0; + pint64 = binn_list_read(ptr, 8, &type, &size); + CU_ASSERT(pint64 != NULL); + CU_ASSERT(type == BINN_INT64); + CU_ASSERT(*pint64 == vint64); + // in the second time the value must be the same... + type = 0; + size = 0; + pint64 = binn_list_read(ptr, 8, &type, &size); + CU_ASSERT(pint64 != NULL); + CU_ASSERT(type == BINN_INT64); + CU_ASSERT(*pint64 == vint64); + + type = 0; + size = 0; + puint64 = binn_list_read(ptr, 9, &type, &size); + CU_ASSERT(puint64 != NULL); + CU_ASSERT(type == BINN_UINT64); + CU_ASSERT(*puint64 == vuint64); + + type = 0; + size = 0; + pstr = binn_list_read(ptr, 10, &type, &size); + CU_ASSERT(pstr != NULL); + CU_ASSERT(type == BINN_STRING); + CU_ASSERT(size > 0); + CU_ASSERT(strcmp(pstr, "this is the string") == 0); + + type = 0; + size = 0; + p2 = binn_list_read(ptr, 11, &type, &size); + CU_ASSERT(p2 != NULL); + CU_ASSERT(type == BINN_BLOB); + CU_ASSERT(size == blobsize); + CU_ASSERT(memcmp(p2, pblob, blobsize) == 0); + + + ptr = binn_ptr(map); + CU_ASSERT(ptr != NULL); + + type = 0; + size = 0; + pstr = binn_map_read(ptr, 99000, &type, &size); + CU_ASSERT(pstr != NULL); + CU_ASSERT(type == BINN_NULL); + //CU_ASSERT(size > 0); + //CU_ASSERT(strcmp(pstr, "this is the value") == 0); + + type = 0; + size = 0; + p2 = binn_map_read(ptr, 99001, &type, &size); + CU_ASSERT(p2 != NULL); + CU_ASSERT(type == BINN_UINT8); + CU_ASSERT(*p2 == vbyte); + + type = 0; + size = 0; + pint16 = binn_map_read(ptr, 99002, &type, &size); + CU_ASSERT(pint16 != NULL); + CU_ASSERT(type == BINN_INT16); + CU_ASSERT(*pint16 == vint16); + + type = 0; + size = 0; + puint16 = binn_map_read(ptr, 99003, &type, &size); + CU_ASSERT(puint16 != NULL); + CU_ASSERT(type == BINN_UINT16); + CU_ASSERT(*puint16 == vuint16); + + type = 0; + size = 0; + pint32 = binn_map_read(ptr, 99004, &type, &size); + CU_ASSERT(pint32 != NULL); + CU_ASSERT(type == BINN_INT32); + CU_ASSERT(*pint32 == vint32); + // in the second time the value must be the same... + type = 0; + size = 0; + pint32 = binn_map_read(ptr, 99004, &type, &size); + CU_ASSERT(pint32 != NULL); + CU_ASSERT(type == BINN_INT32); + CU_ASSERT(*pint32 == vint32); + + type = 0; + size = 0; + puint32 = binn_map_read(ptr, 99005, &type, &size); + CU_ASSERT(puint32 != NULL); + CU_ASSERT(type == BINN_UINT32); + CU_ASSERT(*puint32 == vuint32); + + type = 0; + size = 0; + pint64 = binn_map_read(ptr, 99006, &type, &size); + CU_ASSERT(pint64 != NULL); + CU_ASSERT(type == BINN_INT64); + CU_ASSERT(*pint64 == vint64); + // in the second time the value must be the same... + type = 0; + size = 0; + pint64 = binn_map_read(ptr, 99006, &type, &size); + CU_ASSERT(pint64 != NULL); + CU_ASSERT(type == BINN_INT64); + CU_ASSERT(*pint64 == vint64); + + type = 0; + size = 0; + puint64 = binn_map_read(ptr, 99007, &type, &size); + CU_ASSERT(puint64 != NULL); + CU_ASSERT(type == BINN_UINT64); + CU_ASSERT(*puint64 == vuint64); + + type = 0; + size = 0; + pstr = binn_map_read(ptr, 99008, &type, &size); + CU_ASSERT(pstr != NULL); + CU_ASSERT(type == BINN_STRING); + CU_ASSERT(size > 0); + CU_ASSERT(strcmp(pstr, "this is the string") == 0); + + type = 0; + size = 0; + p2 = binn_map_read(ptr, 99009, &type, &size); + CU_ASSERT(p2 != NULL); + CU_ASSERT(type == BINN_BLOB); + CU_ASSERT(size == blobsize); + CU_ASSERT(memcmp(p2, pblob, blobsize) == 0); + + + ptr = binn_ptr(obj); + CU_ASSERT(ptr != NULL); + + type = 0; + size = 0; + pstr = binn_object_read(ptr, "key0", &type, &size); + CU_ASSERT(pstr != NULL); + CU_ASSERT(type == BINN_NULL); + //CU_ASSERT(size > 0); + //CU_ASSERT(strcmp(pstr, "this is the value") == 0); + + type = 0; + size = 0; + p2 = binn_object_read(ptr, "key1", &type, &size); + CU_ASSERT(p2 != NULL); + CU_ASSERT(type == BINN_UINT8); + CU_ASSERT(*p2 == vbyte); + + type = 0; + size = 0; + pint16 = binn_object_read(ptr, "key2", &type, &size); + CU_ASSERT(pint16 != NULL); + CU_ASSERT(type == BINN_INT16); + CU_ASSERT(*pint16 == vint16); + + type = 0; + size = 0; + puint16 = binn_object_read(ptr, "key3", &type, &size); + CU_ASSERT(puint16 != NULL); + CU_ASSERT(type == BINN_UINT16); + CU_ASSERT(*puint16 == vuint16); + + type = 0; + size = 0; + pint32 = binn_object_read(ptr, "key4", &type, &size); + CU_ASSERT(pint32 != NULL); + CU_ASSERT(type == BINN_INT32); + CU_ASSERT(*pint32 == vint32); + // in the second time the value must be the same... + type = 0; + size = 0; + pint32 = binn_object_read(ptr, "key4", &type, &size); + CU_ASSERT(pint32 != NULL); + CU_ASSERT(type == BINN_INT32); + CU_ASSERT(*pint32 == vint32); + + type = 0; + size = 0; + puint32 = binn_object_read(ptr, "key5", &type, &size); + CU_ASSERT(puint32 != NULL); + CU_ASSERT(type == BINN_UINT32); + CU_ASSERT(*puint32 == vuint32); + + type = 0; + size = 0; + pint64 = binn_object_read(ptr, "key6", &type, &size); + CU_ASSERT(pint64 != NULL); + CU_ASSERT(type == BINN_INT64); + CU_ASSERT(*pint64 == vint64); + // in the second time the value must be the same... + type = 0; + size = 0; + pint64 = binn_object_read(ptr, "key6", &type, &size); + CU_ASSERT(pint64 != NULL); + CU_ASSERT(type == BINN_INT64); + CU_ASSERT(*pint64 == vint64); + + type = 0; + size = 0; + puint64 = binn_object_read(ptr, "key7", &type, &size); + CU_ASSERT(puint64 != NULL); + CU_ASSERT(type == BINN_UINT64); + CU_ASSERT(*puint64 == vuint64); + + type = 0; + size = 0; + pstr = binn_object_read(ptr, "key8", &type, &size); + CU_ASSERT(pstr != NULL); + CU_ASSERT(type == BINN_STRING); + CU_ASSERT(size > 0); + CU_ASSERT(strcmp(pstr, "this is the string") == 0); + + type = 0; + size = 0; + p2 = binn_object_read(ptr, "key9", &type, &size); + CU_ASSERT(p2 != NULL); + CU_ASSERT(type == BINN_BLOB); + CU_ASSERT(size == blobsize); + CU_ASSERT(memcmp(p2, pblob, blobsize) == 0); + + type = 0; + size = 0; + p2 = binn_object_read(ptr, "v2", &type, &size); + CU_ASSERT(p2 != NULL); + CU_ASSERT(type == BINN_STRING); + CU_ASSERT(size == fix_size - 1); + CU_ASSERT(strlen(p2) == fix_size - 1); + CU_ASSERT(p2[0] == 'A'); + CU_ASSERT(p2[1] == 'A'); + CU_ASSERT(p2[500] == 'A'); + CU_ASSERT(p2[fix_size - 1] == 0); + + type = 0; + size = 0; + pstr = binn_object_read(ptr, "key00", &type, &size); + CU_ASSERT(pstr != NULL); + CU_ASSERT(type == BINN_STRING); + CU_ASSERT(size > 0); + CU_ASSERT(strcmp(pstr, "after the big string") == 0); + + type = 0; + size = 0; + p2 = binn_object_read(ptr, "list", &type, &size); + CU_ASSERT(p2 != NULL); + CU_ASSERT(type == BINN_LIST); + CU_ASSERT(size > 0); + // + type = 0; + size = 0; + puint64 = binn_list_read(p2, 9, &type, &size); + CU_ASSERT(puint64 != NULL); + CU_ASSERT(type == BINN_UINT64); + CU_ASSERT(*puint64 == vuint64); + // + type = 0; + size = 0; + pstr = binn_list_read(p2, 10, &type, &size); + CU_ASSERT(pstr != NULL); + CU_ASSERT(type == BINN_STRING); + CU_ASSERT(size > 0); + CU_ASSERT(strcmp(pstr, "this is the string") == 0); + // + type = 0; + size = 0; + pstr = binn_list_read(p2, 12, &type, &size); + CU_ASSERT(pstr != NULL); + CU_ASSERT(type == BINN_BLOB); // empty blob + CU_ASSERT(size == 0); + // + type = 0; + size = 0; + pstr = binn_list_read(p2, 13, &type, &size); + CU_ASSERT(pstr != NULL); + CU_ASSERT(type == BINN_STRING); // empty string + CU_ASSERT(size == 0); + CU_ASSERT(strcmp(pstr, "") == 0); + // + type = 0; + size = 0; + pstr = binn_list_read(p2, 14, &type, &size); + CU_ASSERT(pstr != NULL); + CU_ASSERT(type == BINN_STRING); + CU_ASSERT(size > 0); + CU_ASSERT(strcmp(pstr, "after the empty items") == 0); + + type = 0; + size = 0; + pstr = binn_object_read(ptr, "key10", &type, &size); + CU_ASSERT(pstr != NULL); + CU_ASSERT(type == BINN_STRING); + CU_ASSERT(size > 0); + CU_ASSERT(strcmp(pstr, "after the list") == 0); + + + // binn_ptr, IsValidBinnHeader, binn_is_valid... + // also with invalid/null pointers, with pointers containing invalid data... + + CU_ASSERT(binn_ptr(NULL) == NULL); + // pointers to invalid data + //CU_ASSERT(binn_ptr(&type) == NULL); + //CU_ASSERT(binn_ptr(&size) == NULL); + //CU_ASSERT(binn_ptr(&count) == NULL); + //CU_ASSERT(binn_is_valid_header(NULL) == FALSE); + + ptr = binn_ptr(obj); + CU_ASSERT(ptr != NULL); + // test the header + size = 0; + CU_ASSERT(binn_is_valid_header(ptr, &type, &count, &size, &header_size) == TRUE); + CU_ASSERT(type == BINN_OBJECT); + CU_ASSERT(count == 15); + CU_ASSERT(header_size >= MIN_BINN_SIZE && header_size <= MAX_BINN_HEADER); + CU_ASSERT(size > MIN_BINN_SIZE); + CU_ASSERT(size == obj->size); + // test all the buffer + CU_ASSERT(binn_is_valid(ptr, &type, &count, &size) == TRUE); + CU_ASSERT(type == BINN_OBJECT); + CU_ASSERT(count == 15); + CU_ASSERT(size > MIN_BINN_SIZE); + CU_ASSERT(size == obj->size); + + ptr = binn_ptr(map); + CU_ASSERT(ptr != NULL); + // test the header + size = 0; + CU_ASSERT(binn_is_valid_header(ptr, &type, &count, &size, &header_size) == TRUE); + CU_ASSERT(type == BINN_MAP); + CU_ASSERT(count == 11); + CU_ASSERT(header_size >= MIN_BINN_SIZE && header_size <= MAX_BINN_HEADER); + CU_ASSERT(size > MIN_BINN_SIZE); + CU_ASSERT(size == map->size); + // test all the buffer + CU_ASSERT(binn_is_valid(ptr, &type, &count, &size) == TRUE); + CU_ASSERT(type == BINN_MAP); + CU_ASSERT(count == 11); + CU_ASSERT(size > MIN_BINN_SIZE); + CU_ASSERT(size == map->size); + + ptr = binn_ptr(list); + CU_ASSERT(ptr != NULL); + // test the header + size = 0; + CU_ASSERT(binn_is_valid_header(ptr, &type, &count, &size, &header_size) == TRUE); + CU_ASSERT(type == BINN_LIST); + CU_ASSERT(count == 14); + CU_ASSERT(header_size >= MIN_BINN_SIZE && header_size <= MAX_BINN_HEADER); + CU_ASSERT(size > MIN_BINN_SIZE); + CU_ASSERT(size == list->size); + // test all the buffer + CU_ASSERT(binn_is_valid(ptr, &type, &count, &size) == TRUE); + CU_ASSERT(type == BINN_LIST); + CU_ASSERT(count == 14); + CU_ASSERT(header_size >= MIN_BINN_SIZE && header_size <= MAX_BINN_HEADER); + CU_ASSERT(size > MIN_BINN_SIZE); + CU_ASSERT(size == list->size); + + + // binn_size - invalid and valid args -------------------------------------------- + + CU_ASSERT(binn_size(NULL) == 0); + + CU_ASSERT(binn_size(list) == list->size); + CU_ASSERT(binn_size(map) == map->size); + CU_ASSERT(binn_size(obj) == obj->size); + CU_ASSERT(binn_size(obj1) == obj1->size); + + + // destroy them all ------------------------------------------------------------------- + + binn_free(list); + binn_free(map); + binn_free(obj); + + printf("OK\n"); +} + +/*************************************************************************************/ + +void test_invalid_binn() { + + unsigned char buffers[][20] = { + { 0xE0 }, + { 0xE0, 0x7E}, + { 0xE0, 0x7E, 0x7F}, + { 0xE0, 0x7E, 0x7F, 0x12}, + { 0xE0, 0x7E, 0x7F, 0x12, 0x34}, + { 0xE0, 0x7E, 0x7F, 0x12, 0x34, 0x01}, + { 0xE0, 0x7E, 0x7F, 0x12, 0x34, 0x7F}, + { 0xE0, 0x7E, 0x7F, 0x12, 0x34, 0xFF}, + { 0xE0, 0x7E, 0x7F, 0x12, 0x34, 0xFF, 0xFF}, + { 0xE0, 0x7E, 0x7F, 0x12, 0x34, 0xFF, 0xFF, 0xFF}, + { 0xE0, 0x7E, 0x7F, 0x12, 0x34, 0xFF, 0xFF, 0xFF, 0xFF}, + { 0xE0, 0x7E, 0x7F, 0x12, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0x01}, + { 0xE0, 0x7E, 0x7F, 0x12, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + { 0xE0, 0x7E, 0xFF}, + { 0xE0, 0x7E, 0xFF, 0x12}, + { 0xE0, 0x7E, 0xFF, 0x12, 0x34}, + { 0xE0, 0x7E, 0xFF, 0x12, 0x34, 0x01}, + { 0xE0, 0x7E, 0xFF, 0x12, 0x34, 0x7F}, + { 0xE0, 0x7E, 0xFF, 0x12, 0x34, 0xFF}, + { 0xE0, 0x7E, 0xFF, 0x12, 0x34, 0xFF, 0xFF}, + { 0xE0, 0x7E, 0xFF, 0x12, 0x34, 0xFF, 0xFF, 0xFF}, + { 0xE0, 0x7E, 0xFF, 0x12, 0x34, 0xFF, 0xFF, 0xFF, 0xFF}, + { 0xE0, 0x7E, 0xFF, 0x12, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0x01}, + { 0xE0, 0x7E, 0xFF, 0x12, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + { 0xE0, 0x8E}, + { 0xE0, 0x8E, 0xFF}, + { 0xE0, 0x8E, 0xFF, 0x12}, + { 0xE0, 0x8E, 0xFF, 0x12, 0x34}, + { 0xE0, 0x8E, 0xFF, 0x12, 0x34, 0x01}, + { 0xE0, 0x8E, 0xFF, 0x12, 0x34, 0x7F}, + { 0xE0, 0x8E, 0xFF, 0x12, 0x34, 0xFF}, + { 0xE0, 0x8E, 0xFF, 0x12, 0x34, 0xFF, 0xFF}, + { 0xE0, 0x8E, 0xFF, 0x12, 0x34, 0xFF, 0xFF, 0xFF}, + { 0xE0, 0x8E, 0xFF, 0x12, 0x34, 0xFF, 0xFF, 0xFF, 0xFF}, + { 0xE0, 0x8E, 0xFF, 0x12, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0x01}, + { 0xE0, 0x8E, 0xFF, 0x12, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} + }; + + int count, size, i; + unsigned char *ptr; + + puts("testing invalid binn buffers..."); + + count = sizeof buffers / sizeof buffers[0]; + + for (i = 0; i < count; i++) { + ptr = buffers[i]; + size = strlen((char*) ptr); + printf("checking invalid binn #%d size: %d bytes\n", i, size); + CU_ASSERT(binn_is_valid_ex(ptr, NULL, NULL, &size) == FALSE); + } + + puts("OK"); +} + +int main() { + CU_pSuite pSuite = NULL; + if (CUE_SUCCESS != CU_initialize_registry()) { + return CU_get_error(); + } + pSuite = CU_add_suite("jbl_test_binn1", init_suite, clean_suite); + if (NULL == pSuite) { + CU_cleanup_registry(); + return CU_get_error(); + } + if ( (NULL == CU_add_test(pSuite, "test_int64", test_int64)) + || (NULL == CU_add_test(pSuite, "test_floating_point_numbers", test_floating_point_numbers)) + || (NULL == CU_add_test(pSuite, "test1", test1)) + || (NULL == CU_add_test(pSuite, "test2", test2)) + || (NULL == CU_add_test(pSuite, "test_invalid_binn", test_invalid_binn))) { + CU_cleanup_registry(); + return CU_get_error(); + } + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + int ret = CU_get_error() || CU_get_number_of_failures(); + CU_cleanup_registry(); + return ret; +} diff --git a/src/jbl/tests/jbl_test_binn2.c b/src/jbl/tests/jbl_test_binn2.c new file mode 100644 index 0000000..adf88d7 --- /dev/null +++ b/src/jbl/tests/jbl_test_binn2.c @@ -0,0 +1,2466 @@ +#include "ejdb2.h" +#include "jbl.h" +#include "jbl_internal.h" +#include +#include +#include + +#define INT64_FORMAT PRId64 +#define UINT64_FORMAT PRIu64 +#define INT64_HEX_FORMAT PRIx64 + +typedef unsigned short int u16; +typedef unsigned int u32; +typedef unsigned long long int u64; + +int init_suite(void) { + int rc = ejdb_init(); + return rc; +} + +int clean_suite(void) { + return 0; +} + +#ifdef _MSC_VER +#define snprintf _snprintf +#endif + +int MY_DATE; +int MY_CURRENCY; + +char tmp[128]; + +void *memdup(const void *mem, size_t size) { + void *out = malloc(size); + + if (out != NULL) { + memcpy(out, mem, size); + } + + return out; +} + +char *i64toa(int64 val, char *buf) { + iwitoa(val, buf, 127); + return buf; +} + +#ifdef _MSC_VER +#define atoi64 _atoi64 +#else +int64 atoi64(char *str); + +#endif + +BOOL AlmostEqualFloats(float A, float B, int maxUlps); + +/*************************************************************************************/ + +int vint32; +unsigned int vuint32; +int64 vint64; +uint64 vuint64; +short vint16; +unsigned short vuint16; +signed char vint8; +unsigned char vuint8; +float vfloat32; +double vfloat64; +BOOL vbool; + +/*************************************************************************************/ + +char *stripchr(char *mainstr, int separator) { + char *ptr; + + if (mainstr == NULL) { + return NULL; + } + + ptr = strchr(mainstr, separator); + if (ptr == 0) { + return NULL; + } + ptr[0] = '\0'; + ptr++; + return ptr; +} + +/*************************************************************************************/ +// MY_DATE +// day: 1-31 -> 2^5 -> 5 bits +// month: 1-12 -> 2^4 -> 4 bits +// year: 16 - 9 bits = 7 bits -> 2^7=128 - from 1900 to 2028 + +unsigned short str_to_date(char *datestr) { + unsigned short date; + int day, month, year; + char *next; + + if (datestr == NULL) { + return 0; + } + strcpy(tmp, datestr); + datestr = tmp; + + next = stripchr(datestr, '-'); + year = atoi(datestr) - 1900; + + datestr = next; + next = stripchr(datestr, '-'); + month = atoi(datestr); + + day = atoi(next); + + date = (day << 11) | (month << 7) | year; + return date; +} + +/*************************************************************************************/ + +char *date_to_str(unsigned short date) { + int day, month, year; + //char *datestr; + + day = ((date & 0xf800) >> 11); + month = ((date & 0x0780) >> 7); + year = (date & 0x007f); + + sprintf(tmp, "%.4d-%.2d-%.2d", year + 1900, month, day); + + return tmp; +} + +/*************************************************************************************/ +// MY_CURRENCY +// 00000000.0000 <- fixed point + +#define CURRENCY_DECIMAL_DIGITS 4 +#define CURRENCY_DECIMAL_DIGITS_STR "0000" +#define CURRENCY_FACTOR 10000 + +int64 str_to_currency(char *str) { + char *next; + int size, i; + + if (str == NULL) { + return 0; + } + strcpy(tmp, str); + str = tmp; + + next = strchr(str, '.'); + if (next) { + size = strlen(next + 1); + memmove(next, next + 1, size + 1); // include the null terminator + if (size <= CURRENCY_DECIMAL_DIGITS) { + size = CURRENCY_DECIMAL_DIGITS - size; + for (i = 0; i < size; i++) strcat(str, "0"); + } else { + next[CURRENCY_DECIMAL_DIGITS] = 0; // puts a null terminator + } + } else { + strcat(str, CURRENCY_DECIMAL_DIGITS_STR); + } + + return atoi64(str); +} + +/*************************************************************************************/ + +char *currency_to_str(int64 value) { + char *str, *ptr; + int size, move, i; + + i64toa(value, tmp); + str = tmp; + + size = strlen(str); + if (size > CURRENCY_DECIMAL_DIGITS) { + ptr = str + size - CURRENCY_DECIMAL_DIGITS; + memmove(ptr + 1, ptr, CURRENCY_DECIMAL_DIGITS + 1); // include the null terminator + ptr[0] = '.'; // include the point + } else { + move = 2 + CURRENCY_DECIMAL_DIGITS - size; + memmove(str + move, str, size + 1); // include the null terminator + str[0] = '0'; // include the zero + str[1] = '.'; // include the point + for (i = 2; i < move; i++) str[i] = '0'; + } + + return str; +} + +/*************************************************************************************/ + +int64 float_to_currency(double value) { + char buf[128]; + + snprintf(buf, 127, "%.4f", value); + + return str_to_currency(buf); +} + +/*************************************************************************************/ + +double currency_to_float(int64 value) { + + currency_to_str(value); + + return atof(tmp); +} + +/*************************************************************************************/ + +int64 mul_currency(int64 value1, int64 value2) { + return value1 * value2 / CURRENCY_FACTOR; +} + +/*************************************************************************************/ + +int64 div_currency(int64 value1, int64 value2) { + return value1 * CURRENCY_FACTOR / value2; +} + +/*************************************************************************************/ + +//! this code may not work on processors that does not use the default float standard +// original name: AlmostEqual2sComplement +BOOL AlmostEqualFloats(float A, float B, int maxUlps) { + int aInt, bInt, intDiff; + // Make sure maxUlps is non-negative and small enough that the + // default NAN won't compare as equal to anything. + CU_ASSERT(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); + aInt = *(int*) &A; + bInt = *(int*) &B; + // Make aInt lexicographically ordered as a twos-complement int + if (aInt < 0) { + aInt = 0x80000000 - aInt; + } + if (bInt < 0) { + bInt = 0x80000000 - bInt; + } + intDiff = abs(aInt - bInt); + if (intDiff <= maxUlps) { + return TRUE; + } + return FALSE; +} + +/*************************************************************************************/ + +char *test_create_object_1(int *psize) { + binn *obj = INVALID_BINN, *list = INVALID_BINN; + + printf("creating object 1...\n"); + + obj = binn_object(); + CU_ASSERT(obj != NULL); + + vint32 = -12345; + CU_ASSERT(binn_object_set(obj, "int32", BINN_INT32, &vint32, 0) == TRUE); + vint16 = -258; + CU_ASSERT(binn_object_set(obj, "int16", BINN_INT16, &vint16, 0) == TRUE); + vint8 = -120; + CU_ASSERT(binn_object_set(obj, "int8", BINN_INT8, &vint8, 0) == TRUE); + vint64 = -1234567890123; + CU_ASSERT(binn_object_set(obj, "int64", BINN_INT64, &vint64, 0) == TRUE); + + vuint32 = 123456; + CU_ASSERT(binn_object_set(obj, "uint32", BINN_UINT32, &vuint32, 0) == TRUE); + vuint16 = 60500; + CU_ASSERT(binn_object_set(obj, "uint16", BINN_UINT16, &vuint16, 0) == TRUE); + vuint8 = 250; + CU_ASSERT(binn_object_set(obj, "uint8", BINN_UINT8, &vuint8, 0) == TRUE); + vuint64 = 1234567890123; + CU_ASSERT(binn_object_set(obj, "uint64", BINN_UINT64, &vuint64, 0) == TRUE); + + vfloat32 = -12.345; + CU_ASSERT(binn_object_set(obj, "float32", BINN_FLOAT32, &vfloat32, 0) == TRUE); + vfloat32 = -12.345; + CU_ASSERT(binn_object_set(obj, "single", BINN_SINGLE, &vfloat32, 0) == TRUE); + vfloat64 = -123456.7895; + CU_ASSERT(binn_object_set(obj, "float64", BINN_FLOAT64, &vfloat64, 0) == TRUE); + vfloat64 = -123456.7895; + CU_ASSERT(binn_object_set(obj, "double", BINN_DOUBLE, &vfloat64, 0) == TRUE); + + CU_ASSERT(binn_object_set(obj, "str", BINN_STRING, "the value", 0) == TRUE); + + vint32 = TRUE; + CU_ASSERT(binn_object_set(obj, "bool_true", BINN_BOOL, &vint32, 0) == TRUE); + vint32 = FALSE; + CU_ASSERT(binn_object_set(obj, "bool_false", BINN_BOOL, &vint32, 0) == TRUE); + + CU_ASSERT(binn_object_set(obj, "null", BINN_NULL, NULL, 0) == TRUE); + + + // add a list + + list = binn_list(); + CU_ASSERT(list != NULL); + + CU_ASSERT(binn_list_add(list, BINN_NULL, NULL, 0) == TRUE); + vint32 = 123; + CU_ASSERT(binn_list_add(list, BINN_INT32, &vint32, 0) == TRUE); + CU_ASSERT(binn_list_add(list, BINN_STRING, "this is a string", 0) == TRUE); + + CU_ASSERT(binn_object_set(obj, "list", BINN_LIST, binn_ptr(list), binn_size(list)) == TRUE); + + binn_free(list); + list = INVALID_BINN; + + + // return the values + + *psize = binn_size(obj); + return binn_release(obj); +} + +/*************************************************************************************/ + +char *test_create_object_2(int *psize) { + binn *obj = INVALID_BINN, *list = INVALID_BINN; + + printf("creating object 2...\n"); + + obj = binn_object(); + CU_ASSERT(obj != NULL); + + CU_ASSERT(binn_object_set_int32(obj, "int32", -12345) == TRUE); + CU_ASSERT(binn_object_set_int16(obj, "int16", -258) == TRUE); + CU_ASSERT(binn_object_set_int8(obj, "int8", -120) == TRUE); + CU_ASSERT(binn_object_set_int64(obj, "int64", -1234567890123) == TRUE); + + CU_ASSERT(binn_object_set_uint32(obj, "uint32", 123456) == TRUE); + CU_ASSERT(binn_object_set_int16(obj, "uint16", (short) 60500) == TRUE); + CU_ASSERT(binn_object_set_int8(obj, "uint8", (signed char) 250) == TRUE); + CU_ASSERT(binn_object_set_uint64(obj, "uint64", 1234567890123) == TRUE); + + CU_ASSERT(binn_object_set_float(obj, "float32", -12.345) == TRUE); + vfloat32 = -12.345; + CU_ASSERT(binn_object_set(obj, "single", BINN_SINGLE, &vfloat32, 0) == TRUE); + CU_ASSERT(binn_object_set_double(obj, "float64", -123456.7895) == TRUE); + vfloat64 = -123456.7895; + CU_ASSERT(binn_object_set(obj, "double", BINN_DOUBLE, &vfloat64, 0) == TRUE); + + CU_ASSERT(binn_object_set_str(obj, "str", "the value") == TRUE); + + CU_ASSERT(binn_object_set_bool(obj, "bool_true", TRUE) == TRUE); + CU_ASSERT(binn_object_set_bool(obj, "bool_false", FALSE) == TRUE); + + CU_ASSERT(binn_object_set_null(obj, "null") == TRUE); + + + // add a list + + list = binn_list(); + CU_ASSERT(list != NULL); + + CU_ASSERT(binn_list_add_null(list) == TRUE); + CU_ASSERT(binn_list_add_int32(list, 123) == TRUE); + CU_ASSERT(binn_list_add_str(list, "this is a string") == TRUE); + + CU_ASSERT(binn_object_set_list(obj, "list", list) == TRUE); + + binn_free(list); + list = INVALID_BINN; + + + // return the values + + *psize = binn_size(obj); + return binn_release(obj); +} + +/*************************************************************************************/ + +void test_binn_read(void *objptr) { + void *listptr; + char *ptr; + binn value = { 0 }; + + printf("OK\nreading:\n"); + + vint32 = 0; + CU_ASSERT(binn_object_get(objptr, "int32", BINN_INT32, &vint32, NULL) == TRUE); + printf("int32: %d\n", vint32); + CU_ASSERT(vint32 == -12345); + + vint16 = 0; + CU_ASSERT(binn_object_get(objptr, "int16", BINN_INT16, &vint16, NULL) == TRUE); + printf("int16: %d\n", vint16); + CU_ASSERT(vint16 == -258); + + vint8 = 0; + CU_ASSERT(binn_object_get(objptr, "int8", BINN_INT8, &vint8, NULL) == TRUE); + printf("int8: %d\n", vint8); + CU_ASSERT(vint8 == -120); + + vint64 = 0; + CU_ASSERT(binn_object_get(objptr, "int64", BINN_INT64, &vint64, NULL) == TRUE); + printf("int64: %" INT64_FORMAT "\n", vint64); + CU_ASSERT(vint64 == -1234567890123); + + + vuint32 = 0; + CU_ASSERT(binn_object_get(objptr, "uint32", BINN_UINT32, &vuint32, NULL) == TRUE); + printf("uint32: %d\n", vuint32); + CU_ASSERT(vuint32 == 123456); + + vuint16 = 0; + CU_ASSERT(binn_object_get(objptr, "uint16", BINN_UINT16, &vuint16, NULL) == TRUE); + printf("uint16: %d\n", vuint16); + CU_ASSERT(vuint16 == 60500); + + vuint8 = 0; + CU_ASSERT(binn_object_get(objptr, "uint8", BINN_UINT8, &vuint8, NULL) == TRUE); + printf("uint8: %d\n", vuint8); + CU_ASSERT(vuint8 == 250); + + vuint64 = 0; + CU_ASSERT(binn_object_get(objptr, "uint64", BINN_UINT64, &vuint64, NULL) == TRUE); + printf("uint64: %" UINT64_FORMAT "\n", vuint64); + CU_ASSERT(vuint64 == 1234567890123); + + + vfloat32 = 0; + CU_ASSERT(binn_object_get(objptr, "float32", BINN_FLOAT32, &vfloat32, NULL) == TRUE); + printf("float32: %f\n", vfloat32); + CU_ASSERT(AlmostEqualFloats(vfloat32, -12.345, 2) == TRUE); + + vfloat64 = 0; + CU_ASSERT(binn_object_get(objptr, "float64", BINN_FLOAT64, &vfloat64, NULL) == TRUE); + printf("float64: %f\n", vfloat64); + CU_ASSERT(vfloat64 - -123456.7895 < 0.01); + + vfloat32 = 0; + CU_ASSERT(binn_object_get(objptr, "single", BINN_SINGLE, &vfloat32, NULL) == TRUE); + printf("single: %f\n", vfloat32); + CU_ASSERT(AlmostEqualFloats(vfloat32, -12.345, 2) == TRUE); + + vfloat64 = 0; + CU_ASSERT(binn_object_get(objptr, "double", BINN_DOUBLE, &vfloat64, NULL) == TRUE); + printf("double: %f\n", vfloat64); + CU_ASSERT(vfloat64 - -123456.7895 < 0.01); + + + ptr = 0; + CU_ASSERT(binn_object_get(objptr, "str", BINN_STRING, &ptr, NULL) == TRUE); + printf("ptr: (%p) '%s'\n", (void*) ptr, ptr); + CU_ASSERT(strcmp(ptr, "the value") == 0); + + + vint32 = 999; + CU_ASSERT(binn_object_get(objptr, "bool_true", BINN_BOOL, &vint32, NULL) == TRUE); + printf("bool true: %d\n", vint32); + CU_ASSERT(vint32 == TRUE); + + vint32 = 999; + CU_ASSERT(binn_object_get(objptr, "bool_false", BINN_BOOL, &vint32, NULL) == TRUE); + printf("bool false: %d\n", vint32); + CU_ASSERT(vint32 == FALSE); + + + vint32 = 999; + CU_ASSERT(binn_object_get(objptr, "null", BINN_NULL, &vint32, NULL) == TRUE); + printf("null: %d\n", vint32); + //CU_ASSERT(vint32 == 0); + CU_ASSERT(binn_object_get(objptr, "null", BINN_NULL, NULL, NULL) == TRUE); + + + CU_ASSERT(binn_object_get(objptr, "list", BINN_LIST, &listptr, NULL) == TRUE); + printf("obj ptr: %p list ptr: %p\n", objptr, listptr); + CU_ASSERT(listptr != 0); + CU_ASSERT(listptr > objptr); + + vint32 = 0; + CU_ASSERT(binn_list_get(listptr, 2, BINN_INT32, &vint32, NULL) == TRUE); + printf("int32: %d\n", vint32); + CU_ASSERT(vint32 == 123); + + ptr = 0; + CU_ASSERT(binn_list_get(listptr, 3, BINN_STRING, &ptr, NULL) == TRUE); + printf("ptr: (%p) '%s'\n", (void*) ptr, ptr); + CU_ASSERT(strcmp(ptr, "this is a string") == 0); + + + // short read functions 1 + + vint32 = 0; + CU_ASSERT(binn_object_get_int32(objptr, "int32", &vint32) == TRUE); + printf("int32: %d\n", vint32); + CU_ASSERT(vint32 == -12345); + + vint16 = 0; + CU_ASSERT(binn_object_get_int16(objptr, "int16", &vint16) == TRUE); + printf("int16: %d\n", vint16); + CU_ASSERT(vint16 == -258); + + vint8 = 0; + CU_ASSERT(binn_object_get_int8(objptr, "int8", &vint8) == TRUE); + printf("int8: %d\n", vint8); + CU_ASSERT(vint8 == -120); + + vint64 = 0; + CU_ASSERT(binn_object_get_int64(objptr, "int64", &vint64) == TRUE); + printf("int64: %" INT64_FORMAT "\n", vint64); + CU_ASSERT(vint64 == -1234567890123); + + + vuint32 = 0; + CU_ASSERT(binn_object_get_uint32(objptr, "uint32", &vuint32) == TRUE); + printf("uint32: %d\n", vuint32); + CU_ASSERT(vuint32 == 123456); + + vuint16 = 0; + CU_ASSERT(binn_object_get_uint16(objptr, "uint16", &vuint16) == TRUE); + printf("uint16: %d\n", vuint16); + CU_ASSERT(vuint16 == 60500); + + vuint8 = 0; + CU_ASSERT(binn_object_get_uint8(objptr, "uint8", &vuint8) == TRUE); + printf("uint8: %d\n", vuint8); + CU_ASSERT(vuint8 == 250); + + vuint64 = 0; + CU_ASSERT(binn_object_get_uint64(objptr, "uint64", &vuint64) == TRUE); + printf("uint64: %" UINT64_FORMAT "\n", vuint64); + CU_ASSERT(vuint64 == 1234567890123); + + + vfloat32 = 0; + CU_ASSERT(binn_object_get_float(objptr, "float32", &vfloat32) == TRUE); + printf("float32: %f\n", vfloat32); + CU_ASSERT(AlmostEqualFloats(vfloat32, -12.345, 2) == TRUE); + + vfloat64 = 0; + CU_ASSERT(binn_object_get_double(objptr, "float64", &vfloat64) == TRUE); + printf("float64: %f\n", vfloat64); + CU_ASSERT(AlmostEqualFloats(vfloat32, -12.345, 2) == TRUE); + + + ptr = 0; + CU_ASSERT(binn_object_get_str(objptr, "str", &ptr) == TRUE); + printf("ptr: (%p) '%s'\n", (void*) ptr, ptr); + CU_ASSERT(strcmp(ptr, "the value") == 0); + + + vint32 = 999; + CU_ASSERT(binn_object_get_bool(objptr, "bool_true", &vint32) == TRUE); + printf("bool true: %d\n", vint32); + CU_ASSERT(vint32 == TRUE); + + vint32 = 999; + CU_ASSERT(binn_object_get_bool(objptr, "bool_false", &vint32) == TRUE); + printf("bool false: %d\n", vint32); + CU_ASSERT(vint32 == FALSE); + + + vbool = FALSE; + CU_ASSERT(binn_object_null(objptr, "null") == TRUE); + + CU_ASSERT(binn_object_null(objptr, "bool_true") == FALSE); + + + CU_ASSERT(binn_object_get_list(objptr, "list", &listptr) == TRUE); + printf("obj ptr: %p list ptr: %p\n", objptr, listptr); + CU_ASSERT(listptr != 0); + CU_ASSERT(listptr > objptr); + + vint32 = 0; + CU_ASSERT(binn_list_get_int32(listptr, 2, &vint32) == TRUE); + printf("int32: %d\n", vint32); + CU_ASSERT(vint32 == 123); + + ptr = 0; + CU_ASSERT(binn_list_get_str(listptr, 3, &ptr) == TRUE); + printf("ptr: (%p) '%s'\n", (void*) ptr, ptr); + CU_ASSERT(strcmp(ptr, "this is a string") == 0); + + + // short read functions 2 + + vint32 = binn_object_int32(objptr, "int32"); + printf("int32: %d\n", vint32); + CU_ASSERT(vint32 == -12345); + + vint16 = binn_object_int16(objptr, "int16"); + printf("int16: %d\n", vint16); + CU_ASSERT(vint16 == -258); + + vint8 = binn_object_int8(objptr, "int8"); + printf("int8: %d\n", vint8); + CU_ASSERT(vint8 == -120); + + vint64 = binn_object_int64(objptr, "int64"); + printf("int64: %" INT64_FORMAT "\n", vint64); + CU_ASSERT(vint64 == -1234567890123); + + + vuint32 = binn_object_uint32(objptr, "uint32"); + printf("uint32: %d\n", vuint32); + CU_ASSERT(vuint32 == 123456); + + vuint16 = binn_object_uint16(objptr, "uint16"); + printf("uint16: %d\n", vuint16); + CU_ASSERT(vuint16 == 60500); + + vuint8 = binn_object_uint8(objptr, "uint8"); + printf("uint8: %d\n", vuint8); + CU_ASSERT(vuint8 == 250); + + vuint64 = binn_object_uint64(objptr, "uint64"); + printf("uint64: %" UINT64_FORMAT "\n", vuint64); + CU_ASSERT(vuint64 == 1234567890123); + + + vfloat32 = binn_object_float(objptr, "float32"); + printf("float32: %f\n", vfloat32); + CU_ASSERT(AlmostEqualFloats(vfloat32, -12.345, 2) == TRUE); + + vfloat64 = binn_object_double(objptr, "float64"); + printf("float64: %f\n", vfloat64); + CU_ASSERT(AlmostEqualFloats(vfloat32, -12.345, 2) == TRUE); + + + ptr = binn_object_str(objptr, "str"); + printf("ptr: (%p) '%s'\n", (void*) ptr, ptr); + CU_ASSERT(strcmp(ptr, "the value") == 0); + + + vint32 = binn_object_bool(objptr, "bool_true"); + printf("bool true: %d\n", vint32); + CU_ASSERT(vint32 == TRUE); + + vint32 = binn_object_bool(objptr, "bool_false"); + printf("bool false: %d\n", vint32); + CU_ASSERT(vint32 == FALSE); + + + CU_ASSERT(binn_object_null(objptr, "null") == TRUE); + CU_ASSERT(binn_object_null(objptr, "nonull") == FALSE); + + + listptr = binn_object_list(objptr, "list"); + printf("obj ptr: %p list ptr: %p\n", objptr, listptr); + CU_ASSERT(listptr != 0); + CU_ASSERT(listptr > objptr); + + vint32 = binn_list_int32(listptr, 2); + printf("int32: %d\n", vint32); + CU_ASSERT(vint32 == 123); + + ptr = binn_list_str(listptr, 3); + printf("ptr: (%p) '%s'\n", (void*) ptr, ptr); + CU_ASSERT(strcmp(ptr, "this is a string") == 0); + + + // read as value / binn + + CU_ASSERT(binn_object_get_value(objptr, "int32", &value) == TRUE); +#ifndef BINN_DISABLE_COMPRESS_INT + CU_ASSERT(value.type == BINN_INT16); + CU_ASSERT(value.vint16 == -12345); +#else + CU_ASSERT(value.type == BINN_INT32); + CU_ASSERT(value.vint32 == -12345); +#endif + + CU_ASSERT(binn_object_get_value(objptr, "int16", &value) == TRUE); + CU_ASSERT(value.type == BINN_INT16); + CU_ASSERT(value.vint16 == -258); + + CU_ASSERT(binn_object_get_value(objptr, "int8", &value) == TRUE); + CU_ASSERT(value.type == BINN_INT8); + CU_ASSERT(value.vint8 == -120); + + CU_ASSERT(binn_object_get_value(objptr, "int64", &value) == TRUE); + CU_ASSERT(value.type == BINN_INT64); + CU_ASSERT(value.vint64 == -1234567890123); + + + CU_ASSERT(binn_object_get_value(objptr, "uint32", &value) == TRUE); + CU_ASSERT(value.type == BINN_UINT32); + CU_ASSERT(value.vuint32 == 123456); + + CU_ASSERT(binn_object_get_value(objptr, "uint16", &value) == TRUE); + CU_ASSERT(value.type == BINN_UINT16); + CU_ASSERT(value.vuint16 == 60500); + + CU_ASSERT(binn_object_get_value(objptr, "uint8", &value) == TRUE); + CU_ASSERT(value.type == BINN_UINT8); + CU_ASSERT(value.vuint8 == 250); + + CU_ASSERT(binn_object_get_value(objptr, "uint64", &value) == TRUE); + CU_ASSERT(value.type == BINN_UINT64); + CU_ASSERT(value.vuint64 == 1234567890123); + + puts("reading... OK"); +} + +/*************************************************************************************/ + +void init_udts() { + binn *obj = INVALID_BINN; + unsigned short date; + uint64 value; + void *ptr; + + puts("testing UDTs..."); + + CU_ASSERT(strcmp(date_to_str(str_to_date("1950-08-15")), "1950-08-15") == 0); + CU_ASSERT(strcmp(date_to_str(str_to_date("1900-12-01")), "1900-12-01") == 0); + CU_ASSERT(strcmp(date_to_str(str_to_date("2000-10-31")), "2000-10-31") == 0); + CU_ASSERT(strcmp(date_to_str(str_to_date("2014-03-19")), "2014-03-19") == 0); + + printf("curr=%s\n", currency_to_str(str_to_currency("123.456"))); + printf("curr=%s\n", currency_to_str(str_to_currency("123.45"))); + printf("curr=%s\n", currency_to_str(str_to_currency("123.4"))); + printf("curr=%s\n", currency_to_str(str_to_currency("123."))); + printf("curr=%s\n", currency_to_str(str_to_currency("123"))); + printf("curr=%s\n", currency_to_str(str_to_currency("1.2"))); + printf("curr=%s\n", currency_to_str(str_to_currency("0.987"))); + printf("curr=%s\n", currency_to_str(str_to_currency("0.98"))); + printf("curr=%s\n", currency_to_str(str_to_currency("0.9"))); + printf("curr=%s\n", currency_to_str(str_to_currency("0.0"))); + printf("curr=%s\n", currency_to_str(str_to_currency("0"))); + printf("curr=%s\n", currency_to_str(str_to_currency("123.4567"))); + printf("curr=%s\n", currency_to_str(str_to_currency("123.45678"))); + printf("curr=%s\n", currency_to_str(str_to_currency("123.456789"))); + printf("curr=%s\n", currency_to_str(str_to_currency("0.1234"))); + printf("curr=%s\n", currency_to_str(str_to_currency(".1234"))); + + CU_ASSERT(float_to_currency(2.5) == 25000); + CU_ASSERT(float_to_currency(5) == 50000); + CU_ASSERT(str_to_currency("1.1") == 11000); + CU_ASSERT(str_to_currency("12") == 120000); + CU_ASSERT(mul_currency(20000, 20000) == 40000); + CU_ASSERT(mul_currency(20000, 25000) == 50000); + CU_ASSERT(mul_currency(30000, 40000) == 120000); + CU_ASSERT(div_currency(80000, 20000) == 40000); + CU_ASSERT(div_currency(120000, 40000) == 30000); + CU_ASSERT(div_currency(100000, 40000) == 25000); + + printf("1.1 * 2.5 = %s\n", currency_to_str(mul_currency(str_to_currency("1.1"), float_to_currency(2.5)))); + printf("12 / 5 = %s\n", currency_to_str(div_currency(str_to_currency("12"), float_to_currency(5)))); + + + //CU_ASSERT(binn_register_type(MY_DATE, BINN_STORAGE_WORD) == TRUE); + //CU_ASSERT(binn_register_type(MY_CURRENCY, BINN_STORAGE_QWORD) == TRUE); + + MY_DATE = binn_create_type(BINN_STORAGE_WORD, 0x0a); + MY_CURRENCY = binn_create_type(BINN_STORAGE_QWORD, 0x0a); + + + obj = binn_object(); + CU_ASSERT(obj != NULL); + + date = str_to_date("1950-08-15"); + printf(" date 1: %d %s\n", date, date_to_str(date)); + CU_ASSERT(binn_object_set(obj, "date1", MY_DATE, &date, 0) == TRUE); + CU_ASSERT(binn_object_set(obj, "date1", MY_DATE, &date, 0) == FALSE); + + date = str_to_date("1999-12-31"); + printf(" date 2: %d %s\n", date, date_to_str(date)); + binn_object_set(obj, "date2", MY_DATE, &date, 0); + + + value = str_to_currency("123.456"); + printf(" curr 1: %" UINT64_FORMAT " %s\n", value, currency_to_str(value)); + binn_object_set(obj, "curr1", MY_CURRENCY, &value, 0); + + value = str_to_currency("123.45"); + printf(" curr 2: %" UINT64_FORMAT " %s\n", value, currency_to_str(value)); + binn_object_set(obj, "curr2", MY_CURRENCY, &value, 0); + + value = str_to_currency("12.5"); + printf(" curr 3: %" UINT64_FORMAT " %s\n", value, currency_to_str(value)); + binn_object_set(obj, "curr3", MY_CURRENCY, &value, 0); + + value = str_to_currency("5"); + printf(" curr 4: %" UINT64_FORMAT " %s\n", value, currency_to_str(value)); + binn_object_set(obj, "curr4", MY_CURRENCY, &value, 0); + + value = str_to_currency("0.75"); + printf(" curr 5: %" UINT64_FORMAT " %s\n", value, currency_to_str(value)); + binn_object_set(obj, "curr5", MY_CURRENCY, &value, 0); + + + ptr = binn_ptr(obj); + + + CU_ASSERT(binn_object_get(ptr, "date1", MY_DATE, &date, NULL) == TRUE); + printf(" date 1: %d %s\n", date, date_to_str(date)); + + CU_ASSERT(binn_object_get(ptr, "date2", MY_DATE, &date, NULL) == TRUE); + printf(" date 2: %d %s\n", date, date_to_str(date)); + + + CU_ASSERT(binn_object_get(ptr, "curr1", MY_CURRENCY, &value, NULL) == TRUE); + printf(" curr 1: %" UINT64_FORMAT " %s\n", value, currency_to_str(value)); + + CU_ASSERT(binn_object_get(ptr, "curr2", MY_CURRENCY, &value, NULL) == TRUE); + printf(" curr 2: %" UINT64_FORMAT " %s\n", value, currency_to_str(value)); + + CU_ASSERT(binn_object_get(ptr, "curr3", MY_CURRENCY, &value, NULL) == TRUE); + printf(" curr 3: %" UINT64_FORMAT " %s\n", value, currency_to_str(value)); + + CU_ASSERT(binn_object_get(ptr, "curr4", MY_CURRENCY, &value, NULL) == TRUE); + printf(" curr 4: %" UINT64_FORMAT " %s\n", value, currency_to_str(value)); + + CU_ASSERT(binn_object_get(ptr, "curr5", MY_CURRENCY, &value, NULL) == TRUE); + printf(" curr 5: %" UINT64_FORMAT " %s\n", value, currency_to_str(value)); + + + binn_free(obj); + + puts("testing UDTs... OK"); +} + +/*************************************************************************************/ + +BOOL copy_int_value_tests(void *psource, void *pdest, int source_type, int dest_type); + +void test_int_conversion() { + + printf("testing integer conversion..."); + + // copy negative value to unsigned integer + + vint8 = -110; + vuint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vuint8, BINN_INT8, BINN_UINT8) == FALSE); + CU_ASSERT(vint8 == -110); + CU_ASSERT(vuint8 == 0); + + vint8 = -110; + vuint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vuint16, BINN_INT8, BINN_UINT16) == FALSE); + CU_ASSERT(vint8 == -110); + CU_ASSERT(vuint16 == 0); + + vint8 = -110; + vuint32 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vuint32, BINN_INT8, BINN_UINT32) == FALSE); + CU_ASSERT(vint8 == -110); + CU_ASSERT(vuint32 == 0); + + vint8 = -110; + vuint64 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vuint64, BINN_INT8, BINN_UINT64) == FALSE); + CU_ASSERT(vint8 == -110); + CU_ASSERT(vuint64 == 0); + + + vint16 = -123; + vuint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint16, &vuint8, BINN_INT16, BINN_UINT8) == FALSE); + CU_ASSERT(vint16 == -123); + CU_ASSERT(vuint8 == 0); + + vint16 = -123; + vuint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint16, &vuint16, BINN_INT16, BINN_UINT16) == FALSE); + CU_ASSERT(vint16 == -123); + CU_ASSERT(vuint16 == 0); + + vint16 = -32000; + vuint32 = 0; + CU_ASSERT(copy_int_value_tests(&vint16, &vuint32, BINN_INT16, BINN_UINT32) == FALSE); + CU_ASSERT(vint16 == -32000); + CU_ASSERT(vuint32 == 0); + + vint16 = -32000; + vuint64 = 0; + CU_ASSERT(copy_int_value_tests(&vint16, &vuint64, BINN_INT16, BINN_UINT64) == FALSE); + CU_ASSERT(vint16 == -32000); + CU_ASSERT(vuint64 == 0); + + + vint32 = -123; + vuint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vuint8, BINN_INT32, BINN_UINT8) == FALSE); + CU_ASSERT(vint32 == -123); + CU_ASSERT(vuint8 == 0); + + vint32 = -123; + vuint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vuint16, BINN_INT32, BINN_UINT16) == FALSE); + CU_ASSERT(vint32 == -123); + CU_ASSERT(vuint16 == 0); + + vint32 = -123; + vuint32 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vuint32, BINN_INT32, BINN_UINT32) == FALSE); + CU_ASSERT(vint32 == -123); + CU_ASSERT(vuint32 == 0); + + vint32 = -123; + vuint64 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vuint64, BINN_INT32, BINN_UINT64) == FALSE); + CU_ASSERT(vint32 == -123); + CU_ASSERT(vuint64 == 0); + + + vint64 = -123; + vuint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vuint8, BINN_INT64, BINN_UINT8) == FALSE); + CU_ASSERT(vint64 == -123); + CU_ASSERT(vuint8 == 0); + + vint64 = -123; + vuint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vuint16, BINN_INT64, BINN_UINT16) == FALSE); + CU_ASSERT(vint64 == -123); + CU_ASSERT(vuint16 == 0); + + vint64 = -123; + vuint32 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vuint32, BINN_INT64, BINN_UINT32) == FALSE); + CU_ASSERT(vint64 == -123); + CU_ASSERT(vuint32 == 0); + + vint64 = -123; + vuint64 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vuint64, BINN_INT64, BINN_UINT64) == FALSE); + CU_ASSERT(vint64 == -123); + CU_ASSERT(vuint64 == 0); + + + // copy big negative value to small signed integer + + vint16 = -32000; + vint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint16, &vint8, BINN_INT16, BINN_INT8) == FALSE); + CU_ASSERT(vint16 == -32000); + CU_ASSERT(vint8 == 0); + + + vint32 = -250; + vint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vint8, BINN_INT32, BINN_INT8) == FALSE); + CU_ASSERT(vint32 == -250); + CU_ASSERT(vint8 == 0); + + vint32 = -35000; + vint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vint16, BINN_INT32, BINN_INT16) == FALSE); + CU_ASSERT(vint32 == -35000); + CU_ASSERT(vint16 == 0); + + + vint64 = -250; + vint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vint8, BINN_INT64, BINN_INT8) == FALSE); + CU_ASSERT(vint64 == -250); + CU_ASSERT(vint8 == 0); + + vint64 = -35000; + vint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vint16, BINN_INT64, BINN_INT16) == FALSE); + CU_ASSERT(vint64 == -35000); + CU_ASSERT(vint16 == 0); + + vint64 = -25470000000; + vint32 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vint32, BINN_INT64, BINN_INT32) == FALSE); + CU_ASSERT(vint64 == -25470000000); + CU_ASSERT(vint32 == 0); + + + // copy big positive value to small signed integer + + vint16 = 250; + vint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint16, &vint8, BINN_INT16, BINN_INT8) == FALSE); + CU_ASSERT(vint16 == 250); + CU_ASSERT(vint8 == 0); + + + vint32 = 250; + vint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vint8, BINN_INT32, BINN_INT8) == FALSE); + CU_ASSERT(vint32 == 250); + CU_ASSERT(vint8 == 0); + + vint32 = 35000; + vint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vint16, BINN_INT32, BINN_INT16) == FALSE); + CU_ASSERT(vint32 == 35000); + CU_ASSERT(vint16 == 0); + + + vint64 = 250; + vint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vint8, BINN_INT64, BINN_INT8) == FALSE); + CU_ASSERT(vint64 == 250); + CU_ASSERT(vint8 == 0); + + vint64 = 35000; + vint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vint16, BINN_INT64, BINN_INT16) == FALSE); + CU_ASSERT(vint64 == 35000); + CU_ASSERT(vint16 == 0); + + vint64 = 25470000000; + vint32 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vint32, BINN_INT64, BINN_INT32) == FALSE); + CU_ASSERT(vint64 == 25470000000); + CU_ASSERT(vint32 == 0); + + + // copy big positive value to small unsigned integer + + vint16 = 300; + vuint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint16, &vuint8, BINN_INT16, BINN_UINT8) == FALSE); + CU_ASSERT(vint16 == 300); + CU_ASSERT(vuint8 == 0); + + + vint32 = 300; + vuint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vuint8, BINN_INT32, BINN_UINT8) == FALSE); + CU_ASSERT(vint32 == 300); + CU_ASSERT(vuint8 == 0); + + vint32 = 70000; + vuint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vuint16, BINN_INT32, BINN_UINT16) == FALSE); + CU_ASSERT(vint32 == 70000); + CU_ASSERT(vuint16 == 0); + + + vint64 = 300; + vuint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vuint8, BINN_INT64, BINN_UINT8) == FALSE); + CU_ASSERT(vint64 == 300); + CU_ASSERT(vuint8 == 0); + + vint64 = 70000; + vuint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vuint16, BINN_INT64, BINN_UINT16) == FALSE); + CU_ASSERT(vint64 == 70000); + CU_ASSERT(vuint16 == 0); + + vint64 = 25470000000; + vuint32 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vuint32, BINN_INT64, BINN_UINT32) == FALSE); + CU_ASSERT(vint64 == 25470000000); + CU_ASSERT(vuint32 == 0); + + + // valid numbers -------------------- + + // int8 - copy to signed variable + + vint8 = 123; + vint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vint16, BINN_INT8, BINN_INT16) == TRUE); + CU_ASSERT(vint8 == 123); + CU_ASSERT(vint16 == 123); + + vint8 = -110; + vint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vint16, BINN_INT8, BINN_INT16) == TRUE); + CU_ASSERT(vint8 == -110); + CU_ASSERT(vint16 == -110); + + vint8 = 123; + vint32 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vint32, BINN_INT8, BINN_INT32) == TRUE); + CU_ASSERT(vint8 == 123); + CU_ASSERT(vint32 == 123); + + vint8 = -110; + vint32 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vint32, BINN_INT8, BINN_INT32) == TRUE); + CU_ASSERT(vint8 == -110); + CU_ASSERT(vint32 == -110); + + vint8 = 123; + vint64 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vint64, BINN_INT8, BINN_INT64) == TRUE); + CU_ASSERT(vint8 == 123); + CU_ASSERT(vint64 == 123); + + vint8 = -120; + vint64 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vint64, BINN_INT8, BINN_INT64) == TRUE); + CU_ASSERT(vint8 == -120); + CU_ASSERT(vint64 == -120); + + + // int8 - copy to unsigned variable + + vint8 = 123; + vuint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vuint16, BINN_INT8, BINN_UINT16) == TRUE); + CU_ASSERT(vint8 == 123); + CU_ASSERT(vuint16 == 123); + + vint8 = 123; + vuint32 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vuint32, BINN_INT8, BINN_UINT32) == TRUE); + CU_ASSERT(vint8 == 123); + CU_ASSERT(vuint32 == 123); + + vint8 = 123; + vuint64 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vuint64, BINN_INT8, BINN_UINT64) == TRUE); + CU_ASSERT(vint8 == 123); + CU_ASSERT(vuint64 == 123); + + + // unsigned int8 - copy to signed variable + + vuint8 = 123; + vint16 = 0; + CU_ASSERT(copy_int_value_tests(&vuint8, &vint16, BINN_UINT8, BINN_INT16) == TRUE); + CU_ASSERT(vuint8 == 123); + CU_ASSERT(vint16 == 123); + + vuint8 = 250; + vint16 = 0; + CU_ASSERT(copy_int_value_tests(&vuint8, &vint16, BINN_UINT8, BINN_INT16) == TRUE); + CU_ASSERT(vuint8 == 250); + CU_ASSERT(vint16 == 250); + + vuint8 = 123; + vint32 = 0; + CU_ASSERT(copy_int_value_tests(&vuint8, &vint32, BINN_UINT8, BINN_INT32) == TRUE); + CU_ASSERT(vuint8 == 123); + CU_ASSERT(vint32 == 123); + + vuint8 = 250; + vint32 = 0; + CU_ASSERT(copy_int_value_tests(&vuint8, &vint32, BINN_UINT8, BINN_INT32) == TRUE); + CU_ASSERT(vuint8 == 250); + CU_ASSERT(vint32 == 250); + + vuint8 = 123; + vint64 = 0; + CU_ASSERT(copy_int_value_tests(&vuint8, &vint64, BINN_UINT8, BINN_INT64) == TRUE); + CU_ASSERT(vuint8 == 123); + CU_ASSERT(vint64 == 123); + + vuint8 = 250; + vint64 = 0; + CU_ASSERT(copy_int_value_tests(&vuint8, &vint64, BINN_UINT8, BINN_INT64) == TRUE); + CU_ASSERT(vuint8 == 250); + CU_ASSERT(vint64 == 250); + + + // unsigned int8 - copy to unsigned variable + + vuint8 = 123; + vuint16 = 0; + CU_ASSERT(copy_int_value_tests(&vuint8, &vuint16, BINN_UINT8, BINN_UINT16) == TRUE); + CU_ASSERT(vuint8 == 123); + CU_ASSERT(vuint16 == 123); + + vuint8 = 250; + vuint16 = 0; + CU_ASSERT(copy_int_value_tests(&vuint8, &vuint16, BINN_UINT8, BINN_UINT16) == TRUE); + CU_ASSERT(vuint8 == 250); + CU_ASSERT(vuint16 == 250); + + vuint8 = 123; + vuint32 = 0; + CU_ASSERT(copy_int_value_tests(&vuint8, &vuint32, BINN_UINT8, BINN_UINT32) == TRUE); + CU_ASSERT(vuint8 == 123); + CU_ASSERT(vuint32 == 123); + + vuint8 = 250; + vuint32 = 0; + CU_ASSERT(copy_int_value_tests(&vuint8, &vuint32, BINN_UINT8, BINN_UINT32) == TRUE); + CU_ASSERT(vuint8 == 250); + CU_ASSERT(vuint32 == 250); + + vuint8 = 123; + vuint64 = 0; + CU_ASSERT(copy_int_value_tests(&vuint8, &vuint64, BINN_UINT8, BINN_UINT64) == TRUE); + CU_ASSERT(vuint8 == 123); + CU_ASSERT(vuint64 == 123); + + vuint8 = 250; + vuint64 = 0; + CU_ASSERT(copy_int_value_tests(&vuint8, &vuint64, BINN_UINT8, BINN_UINT64) == TRUE); + CU_ASSERT(vuint8 == 250); + CU_ASSERT(vuint64 == 250); + + + vint16 = 250; + vuint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint16, &vuint8, BINN_INT16, BINN_UINT8) == TRUE); + CU_ASSERT(vint16 == 250); + CU_ASSERT(vuint8 == 250); + + + vint32 = 250; + vuint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vuint8, BINN_INT32, BINN_UINT8) == TRUE); + CU_ASSERT(vint32 == 250); + CU_ASSERT(vuint8 == 250); + + vint32 = 35000; + vuint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vuint16, BINN_INT32, BINN_UINT16) == TRUE); + CU_ASSERT(vint32 == 35000); + CU_ASSERT(vuint16 == 35000); + + + vint64 = 250; + vuint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vuint8, BINN_INT64, BINN_UINT8) == TRUE); + CU_ASSERT(vint64 == 250); + CU_ASSERT(vuint8 == 250); + + vint64 = 35000; + vuint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vuint16, BINN_INT64, BINN_UINT16) == TRUE); + CU_ASSERT(vint64 == 35000); + CU_ASSERT(vuint16 == 35000); + + vint64 = 2147000000; + vuint32 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vuint32, BINN_INT64, BINN_UINT32) == TRUE); + CU_ASSERT(vint64 == 2147000000); + CU_ASSERT(vuint32 == 2147000000); + + + // valid negative values + + + vint8 = -110; + vint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vint16, BINN_INT8, BINN_INT16) == TRUE); + CU_ASSERT(vint8 == -110); + CU_ASSERT(vint16 == -110); + + vint8 = -110; + vint32 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vint32, BINN_INT8, BINN_INT32) == TRUE); + CU_ASSERT(vint8 == -110); + CU_ASSERT(vint32 == -110); + + vint8 = -110; + vint64 = 0; + CU_ASSERT(copy_int_value_tests(&vint8, &vint64, BINN_INT8, BINN_INT64) == TRUE); + CU_ASSERT(vint8 == -110); + CU_ASSERT(vint64 == -110); + + + vint16 = -123; + vint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint16, &vint8, BINN_INT16, BINN_INT8) == TRUE); + CU_ASSERT(vint16 == -123); + CU_ASSERT(vint8 == -123); + + vint16 = -32000; + vint32 = 0; + CU_ASSERT(copy_int_value_tests(&vint16, &vint32, BINN_INT16, BINN_INT32) == TRUE); + CU_ASSERT(vint16 == -32000); + CU_ASSERT(vint32 == -32000); + + vint16 = -32000; + vint64 = 0; + CU_ASSERT(copy_int_value_tests(&vint16, &vint64, BINN_INT16, BINN_INT64) == TRUE); + CU_ASSERT(vint16 == -32000); + CU_ASSERT(vint64 == -32000); + + + vint32 = -123; + vint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vint8, BINN_INT32, BINN_INT8) == TRUE); + CU_ASSERT(vint32 == -123); + CU_ASSERT(vint8 == -123); + + vint32 = -123; + vint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vint16, BINN_INT32, BINN_INT16) == TRUE); + CU_ASSERT(vint32 == -123); + CU_ASSERT(vint16 == -123); + + vint32 = -32000; + vint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vint16, BINN_INT32, BINN_INT16) == TRUE); + CU_ASSERT(vint32 == -32000); + CU_ASSERT(vint16 == -32000); + + vint32 = -123; + vint64 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vint64, BINN_INT32, BINN_INT64) == TRUE); + CU_ASSERT(vint32 == -123); + CU_ASSERT(vint64 == -123); + + vint32 = -2147000000; + vint64 = 0; + CU_ASSERT(copy_int_value_tests(&vint32, &vint64, BINN_INT32, BINN_INT64) == TRUE); + CU_ASSERT(vint32 == -2147000000); + CU_ASSERT(vint64 == -2147000000); + + + vint64 = -123; + vint8 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vint8, BINN_INT64, BINN_INT8) == TRUE); + CU_ASSERT(vint64 == -123); + CU_ASSERT(vint8 == -123); + + vint64 = -250; + vint16 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vint16, BINN_INT64, BINN_INT16) == TRUE); + CU_ASSERT(vint64 == -250); + CU_ASSERT(vint16 == -250); + + vint64 = -35000; + vint32 = 0; + CU_ASSERT(copy_int_value_tests(&vint64, &vint32, BINN_INT64, BINN_INT32) == TRUE); + CU_ASSERT(vint64 == -35000); + CU_ASSERT(vint32 == -35000); + + + puts("OK"); +} + +/*************************************************************************************/ + +void test_binn_int_conversion() { + binn *obj = INVALID_BINN; + void *ptr; + + printf("testing binn integer read conversion... "); + + obj = binn_object(); + CU_ASSERT(obj != NULL); + + CU_ASSERT(binn_object_set_int8(obj, "int8", -8) == TRUE); + CU_ASSERT(binn_object_set_int16(obj, "int16", -16) == TRUE); + CU_ASSERT(binn_object_set_int32(obj, "int32", -32) == TRUE); + CU_ASSERT(binn_object_set_int64(obj, "int64", -64) == TRUE); + + CU_ASSERT(binn_object_set_uint8(obj, "uint8", 111) == TRUE); + CU_ASSERT(binn_object_set_uint16(obj, "uint16", 112) == TRUE); + CU_ASSERT(binn_object_set_uint32(obj, "uint32", 113) == TRUE); + CU_ASSERT(binn_object_set_uint64(obj, "uint64", 114) == TRUE); + + ptr = binn_ptr(obj); + + CU_ASSERT(binn_object_int8(ptr, "int8") == -8); + CU_ASSERT(binn_object_int8(ptr, "int16") == -16); + CU_ASSERT(binn_object_int8(ptr, "int32") == -32); + CU_ASSERT(binn_object_int8(ptr, "int64") == -64); + + CU_ASSERT(binn_object_int16(ptr, "int8") == -8); + CU_ASSERT(binn_object_int16(ptr, "int16") == -16); + CU_ASSERT(binn_object_int16(ptr, "int32") == -32); + CU_ASSERT(binn_object_int16(ptr, "int64") == -64); + + CU_ASSERT(binn_object_int32(ptr, "int8") == -8); + CU_ASSERT(binn_object_int32(ptr, "int16") == -16); + CU_ASSERT(binn_object_int32(ptr, "int32") == -32); + CU_ASSERT(binn_object_int32(ptr, "int64") == -64); + + CU_ASSERT(binn_object_int64(ptr, "int8") == -8); + CU_ASSERT(binn_object_int64(ptr, "int16") == -16); + CU_ASSERT(binn_object_int64(ptr, "int32") == -32); + CU_ASSERT(binn_object_int64(ptr, "int64") == -64); + + + CU_ASSERT(binn_object_int8(ptr, "uint8") == 111); + CU_ASSERT(binn_object_int8(ptr, "uint16") == 112); + CU_ASSERT(binn_object_int8(ptr, "uint32") == 113); + CU_ASSERT(binn_object_int8(ptr, "uint64") == 114); + + CU_ASSERT(binn_object_int16(ptr, "uint8") == 111); + CU_ASSERT(binn_object_int16(ptr, "uint16") == 112); + CU_ASSERT(binn_object_int16(ptr, "uint32") == 113); + CU_ASSERT(binn_object_int16(ptr, "uint64") == 114); + + CU_ASSERT(binn_object_int32(ptr, "uint8") == 111); + CU_ASSERT(binn_object_int32(ptr, "uint16") == 112); + CU_ASSERT(binn_object_int32(ptr, "uint32") == 113); + CU_ASSERT(binn_object_int32(ptr, "uint64") == 114); + + CU_ASSERT(binn_object_int64(ptr, "uint8") == 111); + CU_ASSERT(binn_object_int64(ptr, "uint16") == 112); + CU_ASSERT(binn_object_int64(ptr, "uint32") == 113); + CU_ASSERT(binn_object_int64(ptr, "uint64") == 114); + + + binn_free(obj); + + puts("OK"); +} + +/*************************************************************************************/ + +void test_value_conversion() { + binn *value; + char *ptr, blob[64] = "test blob"; + void *pblob; + int size, vint32; + int64 vint64; + double vdouble; + BOOL vbool; + + printf("testing binn value conversion... "); + + /* test string values */ + + ptr = "static string"; + value = binn_string(ptr, BINN_STATIC); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == ptr); + CU_ASSERT(value->freefn == NULL); + binn_free(value); + + ptr = "transient string"; + value = binn_string(ptr, BINN_TRANSIENT); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr != ptr); + CU_ASSERT(strcmp((char*) value->ptr, ptr) == 0); + CU_ASSERT(value->freefn != NULL); + binn_free(value); + + ptr = strdup("dynamic allocated string"); + value = binn_string(ptr, free); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == ptr); + CU_ASSERT(value->freefn != NULL); + CU_ASSERT(value->freefn == &free); + binn_free(value); + + /* test blob values */ + + size = 64; + pblob = blob; + value = binn_blob(pblob, size, BINN_STATIC); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_BLOB); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == pblob); + CU_ASSERT(value->freefn == NULL); + binn_free(value); + + size = 64; + pblob = blob; + value = binn_blob(pblob, size, BINN_TRANSIENT); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_BLOB); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr != pblob); + CU_ASSERT(memcmp(value->ptr, pblob, size) == 0); + CU_ASSERT(value->freefn != NULL); + binn_free(value); + + size = 64; + pblob = memdup(blob, size); + value = binn_blob(pblob, size, free); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_BLOB); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == pblob); + CU_ASSERT(value->freefn == &free); + binn_free(value); + + + /* test conversions */ + + ptr = "123"; + value = binn_string(ptr, BINN_STATIC); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == ptr); + CU_ASSERT(value->freefn == NULL); + // + CU_ASSERT(binn_get_str(value) == ptr); + CU_ASSERT(binn_get_int32(value, &vint32) == TRUE); + CU_ASSERT(vint32 == 123); + CU_ASSERT(binn_get_int64(value, &vint64) == TRUE); + CU_ASSERT(vint64 == 123); + CU_ASSERT(binn_get_double(value, &vdouble) == TRUE); + CU_ASSERT(AlmostEqualFloats(vdouble, 123, 4) == TRUE); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == TRUE); + // check that the type is the same + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == ptr); + CU_ASSERT(value->freefn == NULL); + // + binn_free(value); + + + ptr = "-456"; + value = binn_string(ptr, BINN_STATIC); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == ptr); + CU_ASSERT(value->freefn == NULL); + // + CU_ASSERT(binn_get_str(value) == ptr); + CU_ASSERT(binn_get_int32(value, &vint32) == TRUE); + CU_ASSERT(vint32 == -456); + CU_ASSERT(binn_get_int64(value, &vint64) == TRUE); + CU_ASSERT(vint64 == -456); + CU_ASSERT(binn_get_double(value, &vdouble) == TRUE); + CU_ASSERT(AlmostEqualFloats(vdouble, -456, 4) == TRUE); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == TRUE); + // check that the type is the same + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == ptr); + CU_ASSERT(value->freefn == NULL); + // + binn_free(value); + + + ptr = "-4.56"; + value = binn_string(ptr, BINN_STATIC); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == ptr); + CU_ASSERT(value->freefn == NULL); + // + CU_ASSERT(binn_get_str(value) == ptr); + CU_ASSERT(binn_get_int32(value, &vint32) == TRUE); + CU_ASSERT(vint32 == -4); + CU_ASSERT(binn_get_int64(value, &vint64) == TRUE); + CU_ASSERT(vint64 == -4); + CU_ASSERT(binn_get_double(value, &vdouble) == TRUE); + CU_ASSERT(AlmostEqualFloats(vdouble, -4.56, 4) == TRUE); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == TRUE); + // check that the type is the same + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == ptr); + CU_ASSERT(value->freefn == NULL); + // + binn_free(value); + + + // to boolean + + ptr = "yes"; + value = binn_string(ptr, BINN_STATIC); + CU_ASSERT(value != NULL); + CU_ASSERT(binn_get_str(value) == ptr); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == TRUE); + binn_free(value); + + ptr = "no"; + value = binn_string(ptr, BINN_STATIC); + CU_ASSERT(value != NULL); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == FALSE); + binn_free(value); + + ptr = "on"; + value = binn_string(ptr, BINN_STATIC); + CU_ASSERT(value != NULL); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == TRUE); + binn_free(value); + + ptr = "off"; + value = binn_string(ptr, BINN_STATIC); + CU_ASSERT(value != NULL); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == FALSE); + binn_free(value); + + ptr = "true"; + value = binn_string(ptr, BINN_STATIC); + CU_ASSERT(value != NULL); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == TRUE); + binn_free(value); + + ptr = "false"; + value = binn_string(ptr, BINN_STATIC); + CU_ASSERT(value != NULL); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == FALSE); + binn_free(value); + + ptr = "1"; + value = binn_string(ptr, BINN_STATIC); + CU_ASSERT(value != NULL); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == TRUE); + binn_free(value); + + ptr = "0"; + value = binn_string(ptr, BINN_STATIC); + CU_ASSERT(value != NULL); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == FALSE); + binn_free(value); + + + // from int32 + + value = binn_int32(-345); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_INT32); + CU_ASSERT(value->vint32 == -345); + CU_ASSERT(value->freefn == NULL); + // + CU_ASSERT(binn_get_int32(value, &vint32) == TRUE); + CU_ASSERT(vint32 == -345); + CU_ASSERT(binn_get_int64(value, &vint64) == TRUE); + CU_ASSERT(vint64 == -345); + CU_ASSERT(binn_get_double(value, &vdouble) == TRUE); + CU_ASSERT(AlmostEqualFloats(vdouble, -345, 4) == TRUE); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == TRUE); + // check that the type is the same + CU_ASSERT(value->type == BINN_INT32); + CU_ASSERT(value->vint32 == -345); + CU_ASSERT(value->freefn == NULL); + // convert the value to string + ptr = binn_get_str(value); + CU_ASSERT(ptr != NULL); + CU_ASSERT(strcmp(ptr, "-345") == 0); + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == ptr); + CU_ASSERT(value->freefn != NULL); + // + binn_free(value); + + + value = binn_int32(0); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_INT32); + CU_ASSERT(value->vint32 == 0); + CU_ASSERT(value->freefn == NULL); + // + CU_ASSERT(binn_get_int32(value, &vint32) == TRUE); + CU_ASSERT(vint32 == 0); + CU_ASSERT(binn_get_int64(value, &vint64) == TRUE); + CU_ASSERT(vint64 == 0); + CU_ASSERT(binn_get_double(value, &vdouble) == TRUE); + CU_ASSERT(AlmostEqualFloats(vdouble, 0, 4) == TRUE); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == FALSE); + // check that the type is the same + CU_ASSERT(value->type == BINN_INT32); + CU_ASSERT(value->vint32 == 0); + CU_ASSERT(value->freefn == NULL); + // convert the value to string + ptr = binn_get_str(value); + CU_ASSERT(ptr != NULL); + CU_ASSERT(strcmp(ptr, "0") == 0); + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == ptr); + CU_ASSERT(value->freefn != NULL); + // + binn_free(value); + + + // from int64 + + value = binn_int64(-345678); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_INT64); + CU_ASSERT(value->vint64 == -345678); + CU_ASSERT(value->freefn == NULL); + // + CU_ASSERT(binn_get_int32(value, &vint32) == TRUE); + CU_ASSERT(vint32 == -345678); + CU_ASSERT(binn_get_int64(value, &vint64) == TRUE); + CU_ASSERT(vint64 == -345678); + CU_ASSERT(binn_get_double(value, &vdouble) == TRUE); + CU_ASSERT(AlmostEqualFloats(vdouble, -345678, 4) == TRUE); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == TRUE); + // check that the type is the same + CU_ASSERT(value->type == BINN_INT64); + CU_ASSERT(value->vint64 == -345678); + CU_ASSERT(value->freefn == NULL); + // convert the value to string + ptr = binn_get_str(value); + CU_ASSERT(ptr != NULL); + CU_ASSERT(strcmp(ptr, "-345678") == 0); + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == ptr); + CU_ASSERT(value->freefn != NULL); + // + binn_free(value); + + + // from double + + value = binn_double(-345.678); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_DOUBLE); + CU_ASSERT(value->vdouble == -345.678); + CU_ASSERT(value->freefn == NULL); + // + CU_ASSERT(binn_get_int32(value, &vint32) == TRUE); + CU_ASSERT(vint32 == -345); + CU_ASSERT(binn_get_int64(value, &vint64) == TRUE); + CU_ASSERT(vint64 == -345); + CU_ASSERT(binn_get_double(value, &vdouble) == TRUE); + CU_ASSERT(AlmostEqualFloats(vdouble, -345.678, 4) == TRUE); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == TRUE); + // check that the type is the same + CU_ASSERT(value->type == BINN_DOUBLE); + CU_ASSERT(value->vdouble == -345.678); + CU_ASSERT(value->freefn == NULL); + // convert the value to string + ptr = binn_get_str(value); + CU_ASSERT(ptr != NULL); + CU_ASSERT(strcmp(ptr, "-345.678") == 0); + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == ptr); + CU_ASSERT(value->freefn != NULL); + // + binn_free(value); + + + value = binn_double(0.0); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_DOUBLE); + CU_ASSERT(value->vdouble == 0.0); + CU_ASSERT(value->freefn == NULL); + // + CU_ASSERT(binn_get_int32(value, &vint32) == TRUE); + CU_ASSERT(vint32 == 0); + CU_ASSERT(binn_get_int64(value, &vint64) == TRUE); + CU_ASSERT(vint64 == 0); + CU_ASSERT(binn_get_double(value, &vdouble) == TRUE); + CU_ASSERT(AlmostEqualFloats(vdouble, 0, 4) == TRUE); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == FALSE); + // check that the type is the same + CU_ASSERT(value->type == BINN_DOUBLE); + CU_ASSERT(value->vdouble == 0.0); + CU_ASSERT(value->freefn == NULL); + // convert the value to string + ptr = binn_get_str(value); + CU_ASSERT(ptr != NULL); + CU_ASSERT(strcmp(ptr, "0") == 0); + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == ptr); + CU_ASSERT(value->freefn != NULL); + // + binn_free(value); + + + // from bool + + value = binn_bool(FALSE); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_BOOL); + CU_ASSERT(value->vbool == FALSE); + CU_ASSERT(value->freefn == NULL); + // + CU_ASSERT(binn_get_int32(value, &vint32) == TRUE); + CU_ASSERT(vint32 == 0); + CU_ASSERT(binn_get_int64(value, &vint64) == TRUE); + CU_ASSERT(vint64 == 0); + CU_ASSERT(binn_get_double(value, &vdouble) == TRUE); + CU_ASSERT(AlmostEqualFloats(vdouble, 0, 4) == TRUE); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == FALSE); + // check that the type is the same + CU_ASSERT(value->type == BINN_BOOL); + CU_ASSERT(value->vbool == FALSE); + CU_ASSERT(value->freefn == NULL); + // convert the value to string + ptr = binn_get_str(value); + CU_ASSERT(ptr != NULL); + CU_ASSERT(strcmp(ptr, "false") == 0); + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == ptr); + CU_ASSERT(value->freefn != NULL); + // + binn_free(value); + + + value = binn_bool(TRUE); + CU_ASSERT(value != NULL); + CU_ASSERT(value->type == BINN_BOOL); + CU_ASSERT(value->vbool == TRUE); + CU_ASSERT(value->freefn == NULL); + // + CU_ASSERT(binn_get_int32(value, &vint32) == TRUE); + CU_ASSERT(vint32 == 1); + CU_ASSERT(binn_get_int64(value, &vint64) == TRUE); + CU_ASSERT(vint64 == 1); + CU_ASSERT(binn_get_double(value, &vdouble) == TRUE); + CU_ASSERT(AlmostEqualFloats(vdouble, 1, 4) == TRUE); + CU_ASSERT(binn_get_bool(value, &vbool) == TRUE); + CU_ASSERT(vbool == TRUE); + // check that the type is the same + CU_ASSERT(value->type == BINN_BOOL); + CU_ASSERT(value->vbool == TRUE); + CU_ASSERT(value->freefn == NULL); + // convert the value to string + ptr = binn_get_str(value); + CU_ASSERT(ptr != NULL); + CU_ASSERT(strcmp(ptr, "true") == 0); + CU_ASSERT(value->type == BINN_STRING); + CU_ASSERT(value->ptr != NULL); + CU_ASSERT(value->ptr == ptr); + CU_ASSERT(value->freefn != NULL); + // + binn_free(value); + + + puts("OK"); +} + +/*************************************************************************************/ + +void test_value_copy() { + + printf("testing binn value copy... "); + + //TODO + + puts("TODO!!!"); +} + +/*************************************************************************************/ + +void test_virtual_types() { + binn *list = INVALID_BINN; + void *ptr; + int storage_type, extra_type; + BOOL value; + + printf("testing binn virtual types... "); + + CU_ASSERT(binn_get_type_info(BINN_BOOL, &storage_type, &extra_type) == TRUE); + CU_ASSERT(storage_type == BINN_STORAGE_DWORD); + CU_ASSERT(extra_type == 1); + + list = binn_list(); + CU_ASSERT(list != NULL); + + CU_ASSERT(binn_list_add_bool(list, TRUE) == TRUE); + CU_ASSERT(binn_list_add_bool(list, FALSE) == TRUE); + CU_ASSERT(binn_list_add_null(list) == TRUE); + + ptr = binn_ptr(list); + CU_ASSERT(ptr != 0); + + CU_ASSERT(binn_list_get_bool(ptr, 1, &value) == TRUE); + CU_ASSERT(value == TRUE); + + CU_ASSERT(binn_list_get_bool(ptr, 2, &value) == TRUE); + CU_ASSERT(value == FALSE); + + CU_ASSERT(binn_list_null(ptr, 3) == TRUE); + + // invalid values + CU_ASSERT(binn_list_null(ptr, 1) == FALSE); + CU_ASSERT(binn_list_null(ptr, 2) == FALSE); + CU_ASSERT(binn_list_get_bool(ptr, 3, &value) == FALSE); + + binn_free(list); + + puts("OK"); +} + +/*************************************************************************************/ + +void test_binn_iter() { + binn *list, *map, *obj; + binn *list2, *copy = NULL; + binn_iter iter, iter2; + binn value, value2; + int blob_size, id, id2, list2size; + void *ptr, *blob_ptr; + char key[256], key2[256]; + + blob_ptr = "key\0value\0\0"; + blob_size = 11; + + printf("testing binn sequential read"); + + // create the + + list = binn_list(); + list2 = binn_list(); + map = binn_map(); + obj = binn_object(); + + CU_ASSERT(list != NULL); + CU_ASSERT(list2 != NULL); + CU_ASSERT(map != NULL); + CU_ASSERT(obj != NULL); + + CU_ASSERT(binn_list_add_int32(list2, 250) == TRUE); + CU_ASSERT(binn_list_add_null(list2) == TRUE); + CU_ASSERT(binn_list_add_str(list2, "l1st2") == TRUE); + CU_ASSERT(binn_list_add_bool(list2, TRUE) == TRUE); + + list2size = binn_size(list2); + + CU_ASSERT(binn_list_add_int8(list, 111) == TRUE); + CU_ASSERT(binn_list_add_int32(list, 123456789) == TRUE); + CU_ASSERT(binn_list_add_int16(list, -123) == TRUE); + CU_ASSERT(binn_list_add_int64(list, 9876543210) == TRUE); + CU_ASSERT(binn_list_add_float(list, 1.25) == TRUE); + CU_ASSERT(binn_list_add_double(list, 25.987654321) == TRUE); + CU_ASSERT(binn_list_add_bool(list, TRUE) == TRUE); + CU_ASSERT(binn_list_add_bool(list, FALSE) == TRUE); + CU_ASSERT(binn_list_add_null(list) == TRUE); + CU_ASSERT(binn_list_add_str(list, "testing...") == TRUE); + CU_ASSERT(binn_list_add_blob(list, (char*) blob_ptr, blob_size) == TRUE); + CU_ASSERT(binn_list_add_list(list, list2) == TRUE); + + CU_ASSERT(binn_object_set_int8(obj, "a", 111) == TRUE); + CU_ASSERT(binn_object_set_int32(obj, "b", 123456789) == TRUE); + CU_ASSERT(binn_object_set_int16(obj, "c", -123) == TRUE); + CU_ASSERT(binn_object_set_int64(obj, "d", 9876543210) == TRUE); + CU_ASSERT(binn_object_set_float(obj, "e", 1.25) == TRUE); + CU_ASSERT(binn_object_set_double(obj, "f", 25.987654321) == TRUE); + CU_ASSERT(binn_object_set_bool(obj, "g", TRUE) == TRUE); + CU_ASSERT(binn_object_set_bool(obj, "h", FALSE) == TRUE); + CU_ASSERT(binn_object_set_null(obj, "i") == TRUE); + CU_ASSERT(binn_object_set_str(obj, "j", "testing...") == TRUE); + CU_ASSERT(binn_object_set_blob(obj, "k", (char*) blob_ptr, blob_size) == TRUE); + CU_ASSERT(binn_object_set_list(obj, "l", list2) == TRUE); + + CU_ASSERT(binn_map_set_int8(map, 55010, 111) == TRUE); + CU_ASSERT(binn_map_set_int32(map, 55020, 123456789) == TRUE); + CU_ASSERT(binn_map_set_int16(map, 55030, -123) == TRUE); + CU_ASSERT(binn_map_set_int64(map, 55040, 9876543210) == TRUE); + CU_ASSERT(binn_map_set_float(map, 55050, 1.25) == TRUE); + CU_ASSERT(binn_map_set_double(map, 55060, 25.987654321) == TRUE); + CU_ASSERT(binn_map_set_bool(map, 55070, TRUE) == TRUE); + CU_ASSERT(binn_map_set_bool(map, 55080, FALSE) == TRUE); + CU_ASSERT(binn_map_set_null(map, 55090) == TRUE); + CU_ASSERT(binn_map_set_str(map, 55100, "testing...") == TRUE); + CU_ASSERT(binn_map_set_blob(map, 55110, (char*) blob_ptr, blob_size) == TRUE); + CU_ASSERT(binn_map_set_list(map, 55120, list2) == TRUE); + + + // read list sequentially - using value + + /* + CU_ASSERT(binn_iter_init(&iter, binn_ptr(list), BINN_LIST)); + + while (binn_list_next(&iter, &value)) { + ... + } + */ + + ptr = binn_ptr(list); + CU_ASSERT(ptr != 0); + CU_ASSERT(binn_iter_init(&iter, ptr, BINN_LIST)); + CU_ASSERT(iter.pnext > (unsigned char*) ptr); + CU_ASSERT(iter.plimit > (unsigned char*) ptr); + CU_ASSERT(iter.count == 12); + CU_ASSERT(iter.current == 0); + + CU_ASSERT(binn_list_next(&iter, &value) == TRUE); + CU_ASSERT(iter.pnext > (unsigned char*) ptr); + CU_ASSERT(iter.plimit > (unsigned char*) ptr); + CU_ASSERT(iter.count == 12); + CU_ASSERT(iter.current == 1); + CU_ASSERT(value.type == BINN_INT8); + CU_ASSERT(value.vint8 == 111); + + CU_ASSERT(binn_list_next(&iter, &value) == TRUE); + CU_ASSERT(iter.current == 2); + + CU_ASSERT(value.type == BINN_UINT32); + CU_ASSERT(value.vint32 == 123456789); + + CU_ASSERT(binn_list_next(&iter, &value) == TRUE); + CU_ASSERT(iter.current == 3); + + CU_ASSERT(value.type == BINN_INT8); + CU_ASSERT(value.vint8 == -123); + + CU_ASSERT(binn_list_next(&iter, &value) == TRUE); + CU_ASSERT(iter.current == 4); + CU_ASSERT(value.type == BINN_INT64); + CU_ASSERT(value.vint64 == 9876543210); + + CU_ASSERT(binn_list_next(&iter, &value) == TRUE); + CU_ASSERT(iter.current == 5); + CU_ASSERT(value.type == BINN_FLOAT32); + CU_ASSERT(AlmostEqualFloats(value.vfloat, 1.25, 2)); + + CU_ASSERT(binn_list_next(&iter, &value) == TRUE); + CU_ASSERT(iter.current == 6); + CU_ASSERT(value.type == BINN_FLOAT64); + CU_ASSERT(value.vdouble - 25.987654321 < 0.00000001); + + CU_ASSERT(binn_list_next(&iter, &value) == TRUE); + CU_ASSERT(iter.current == 7); + CU_ASSERT(value.type == BINN_BOOL); + CU_ASSERT(value.vbool == TRUE); + + CU_ASSERT(binn_list_next(&iter, &value) == TRUE); + CU_ASSERT(iter.current == 8); + CU_ASSERT(value.type == BINN_BOOL); + CU_ASSERT(value.vbool == FALSE); + + CU_ASSERT(binn_list_next(&iter, &value) == TRUE); + CU_ASSERT(iter.current == 9); + CU_ASSERT(value.type == BINN_NULL); + + CU_ASSERT(binn_list_next(&iter, &value) == TRUE); + CU_ASSERT(iter.current == 10); + CU_ASSERT(value.type == BINN_STRING); + CU_ASSERT(strcmp((char*) value.ptr, "testing...") == 0); + + CU_ASSERT(binn_list_next(&iter, &value) == TRUE); + CU_ASSERT(iter.current == 11); + CU_ASSERT(value.type == BINN_BLOB); + CU_ASSERT(memcmp(value.ptr, blob_ptr, blob_size) == 0); + + CU_ASSERT(binn_list_next(&iter, &value) == TRUE); + CU_ASSERT(iter.current == 12); + CU_ASSERT(value.type == BINN_LIST); + CU_ASSERT(value.size == list2size); + CU_ASSERT(value.count == 4); + CU_ASSERT(value.ptr != 0); + CU_ASSERT(binn_list_int32(value.ptr, 1) == 250); + CU_ASSERT(binn_list_null(value.ptr, 2) == TRUE); + ptr = binn_list_str(value.ptr, 3); + CU_ASSERT(ptr != 0); + CU_ASSERT(strcmp((char*) ptr, "l1st2") == 0); + CU_ASSERT(binn_list_bool(value.ptr, 4) == TRUE); + + CU_ASSERT(binn_list_next(&iter, &value) == FALSE); + //CU_ASSERT(iter.current == 13); + //CU_ASSERT(iter.count == 12); + + CU_ASSERT(binn_list_next(&iter, &value) == FALSE); + //CU_ASSERT(iter.current == 13); // must keep the same position + //CU_ASSERT(iter.count == 12); + + + // read object sequentially - using value + + ptr = binn_ptr(obj); + CU_ASSERT(ptr != 0); + CU_ASSERT(binn_iter_init(&iter, ptr, BINN_OBJECT)); + CU_ASSERT(iter.pnext > (unsigned char*) ptr); + CU_ASSERT(iter.plimit > (unsigned char*) ptr); + CU_ASSERT(iter.count == 12); + CU_ASSERT(iter.current == 0); + + CU_ASSERT(binn_object_next(&iter, key, &value) == TRUE); + CU_ASSERT(iter.pnext > (unsigned char*) ptr); + CU_ASSERT(iter.plimit > (unsigned char*) ptr); + CU_ASSERT(iter.count == 12); + CU_ASSERT(iter.current == 1); + CU_ASSERT(value.type == BINN_INT8); + CU_ASSERT(value.vint8 == 111); + //printf("%s ", key); + CU_ASSERT(strcmp(key, "a") == 0); + + CU_ASSERT(binn_object_next(&iter, key, &value) == TRUE); + CU_ASSERT(iter.current == 2); + CU_ASSERT(value.type == BINN_UINT32); + CU_ASSERT(value.vint32 == 123456789); + //printf("%s ", key); + CU_ASSERT(strcmp(key, "b") == 0); + + CU_ASSERT(binn_object_next(&iter, key, &value) == TRUE); + CU_ASSERT(iter.current == 3); + + CU_ASSERT(value.type == BINN_INT8); + CU_ASSERT(value.vint8 == -123); + + //printf("%s ", key); + CU_ASSERT(strcmp(key, "c") == 0); + + CU_ASSERT(binn_object_next(&iter, key, &value) == TRUE); + CU_ASSERT(iter.current == 4); + CU_ASSERT(value.type == BINN_INT64); + CU_ASSERT(value.vint64 == 9876543210); + //printf("%s ", key); + CU_ASSERT(strcmp(key, "d") == 0); + + CU_ASSERT(binn_object_next(&iter, key, &value) == TRUE); + CU_ASSERT(iter.current == 5); + CU_ASSERT(value.type == BINN_FLOAT32); + CU_ASSERT(AlmostEqualFloats(value.vfloat, 1.25, 2)); + //printf("%s ", key); + CU_ASSERT(strcmp(key, "e") == 0); + + CU_ASSERT(binn_object_next(&iter, key, &value) == TRUE); + CU_ASSERT(iter.current == 6); + CU_ASSERT(value.type == BINN_FLOAT64); + CU_ASSERT(value.vdouble - 25.987654321 < 0.00000001); + //printf("%s ", key); + CU_ASSERT(strcmp(key, "f") == 0); + + CU_ASSERT(binn_object_next(&iter, key, &value) == TRUE); + CU_ASSERT(iter.current == 7); + CU_ASSERT(value.type == BINN_BOOL); + CU_ASSERT(value.vbool == TRUE); + //printf("%s ", key); + CU_ASSERT(strcmp(key, "g") == 0); + + CU_ASSERT(binn_object_next(&iter, key, &value) == TRUE); + CU_ASSERT(iter.current == 8); + CU_ASSERT(value.type == BINN_BOOL); + CU_ASSERT(value.vbool == FALSE); + //printf("%s ", key); + CU_ASSERT(strcmp(key, "h") == 0); + + CU_ASSERT(binn_object_next(&iter, key, &value) == TRUE); + CU_ASSERT(iter.current == 9); + CU_ASSERT(value.type == BINN_NULL); + //printf("%s ", key); + CU_ASSERT(strcmp(key, "i") == 0); + + CU_ASSERT(binn_object_next(&iter, key, &value) == TRUE); + CU_ASSERT(iter.current == 10); + CU_ASSERT(value.type == BINN_STRING); + CU_ASSERT(strcmp((char*) value.ptr, "testing...") == 0); + //printf("%s ", key); + CU_ASSERT(strcmp(key, "j") == 0); + + CU_ASSERT(binn_object_next(&iter, key, &value) == TRUE); + CU_ASSERT(iter.current == 11); + CU_ASSERT(value.type == BINN_BLOB); + CU_ASSERT(memcmp(value.ptr, blob_ptr, blob_size) == 0); + //printf("%s ", key); + CU_ASSERT(strcmp(key, "k") == 0); + + CU_ASSERT(binn_object_next(&iter, key, &value) == TRUE); + CU_ASSERT(iter.current == 12); + CU_ASSERT(value.type == BINN_LIST); + CU_ASSERT(value.size == list2size); + CU_ASSERT(value.count == 4); + CU_ASSERT(value.ptr != 0); + CU_ASSERT(binn_list_int32(value.ptr, 1) == 250); + CU_ASSERT(binn_list_null(value.ptr, 2) == TRUE); + ptr = binn_list_str(value.ptr, 3); + CU_ASSERT(ptr != 0); + CU_ASSERT(strcmp((char*) ptr, "l1st2") == 0); + CU_ASSERT(binn_list_bool(value.ptr, 4) == TRUE); + //printf("%s ", key); + CU_ASSERT(strcmp(key, "l") == 0); + + CU_ASSERT(binn_object_next(&iter, key, &value) == FALSE); + //CU_ASSERT(iter.current == 13); + //CU_ASSERT(iter.count == 12); + + CU_ASSERT(binn_object_next(&iter, key, &value) == FALSE); + //CU_ASSERT(iter.current == 13); // must keep the same position + //CU_ASSERT(iter.count == 12); + + + // read map sequentially - using value + + ptr = binn_ptr(map); + CU_ASSERT(ptr != 0); + CU_ASSERT(binn_iter_init(&iter, ptr, BINN_MAP)); + CU_ASSERT(iter.pnext > (unsigned char*) ptr); + CU_ASSERT(iter.plimit > (unsigned char*) ptr); + CU_ASSERT(iter.count == 12); + CU_ASSERT(iter.current == 0); + + CU_ASSERT(binn_map_next(&iter, &id, &value) == TRUE); + CU_ASSERT(iter.pnext > (unsigned char*) ptr); + CU_ASSERT(iter.plimit > (unsigned char*) ptr); + CU_ASSERT(iter.count == 12); + CU_ASSERT(iter.current == 1); + CU_ASSERT(value.type == BINN_INT8); + CU_ASSERT(value.vint8 == 111); + CU_ASSERT(id == 55010); + + CU_ASSERT(binn_map_next(&iter, &id, &value) == TRUE); + CU_ASSERT(iter.current == 2); + + CU_ASSERT(value.type == BINN_UINT32); + + CU_ASSERT(value.vint32 == 123456789); + CU_ASSERT(id == 55020); + + CU_ASSERT(binn_map_next(&iter, &id, &value) == TRUE); + CU_ASSERT(iter.current == 3); + + CU_ASSERT(value.type == BINN_INT8); + CU_ASSERT(value.vint8 == -123); + + CU_ASSERT(id == 55030); + + CU_ASSERT(binn_map_next(&iter, &id, &value) == TRUE); + CU_ASSERT(iter.current == 4); + CU_ASSERT(value.type == BINN_INT64); + CU_ASSERT(value.vint64 == 9876543210); + CU_ASSERT(id == 55040); + + CU_ASSERT(binn_map_next(&iter, &id, &value) == TRUE); + CU_ASSERT(iter.current == 5); + CU_ASSERT(value.type == BINN_FLOAT32); + CU_ASSERT(AlmostEqualFloats(value.vfloat, 1.25, 2)); + CU_ASSERT(id == 55050); + + CU_ASSERT(binn_map_next(&iter, &id, &value) == TRUE); + CU_ASSERT(iter.current == 6); + CU_ASSERT(value.type == BINN_FLOAT64); + CU_ASSERT(value.vdouble - 25.987654321 < 0.00000001); + CU_ASSERT(id == 55060); + + CU_ASSERT(binn_map_next(&iter, &id, &value) == TRUE); + CU_ASSERT(iter.current == 7); + CU_ASSERT(value.type == BINN_BOOL); + CU_ASSERT(value.vbool == TRUE); + CU_ASSERT(id == 55070); + + CU_ASSERT(binn_map_next(&iter, &id, &value) == TRUE); + CU_ASSERT(iter.current == 8); + CU_ASSERT(value.type == BINN_BOOL); + CU_ASSERT(value.vbool == FALSE); + CU_ASSERT(id == 55080); + + CU_ASSERT(binn_map_next(&iter, &id, &value) == TRUE); + CU_ASSERT(iter.current == 9); + CU_ASSERT(value.type == BINN_NULL); + CU_ASSERT(id == 55090); + + CU_ASSERT(binn_map_next(&iter, &id, &value) == TRUE); + CU_ASSERT(iter.current == 10); + CU_ASSERT(value.type == BINN_STRING); + CU_ASSERT(strcmp((char*) value.ptr, "testing...") == 0); + CU_ASSERT(id == 55100); + + CU_ASSERT(binn_map_next(&iter, &id, &value) == TRUE); + CU_ASSERT(iter.current == 11); + CU_ASSERT(value.type == BINN_BLOB); + CU_ASSERT(memcmp(value.ptr, blob_ptr, blob_size) == 0); + CU_ASSERT(id == 55110); + + CU_ASSERT(binn_map_next(&iter, &id, &value) == TRUE); + CU_ASSERT(iter.current == 12); + CU_ASSERT(value.type == BINN_LIST); + CU_ASSERT(value.size == list2size); + CU_ASSERT(value.count == 4); + CU_ASSERT(value.ptr != 0); + CU_ASSERT(binn_list_int32(value.ptr, 1) == 250); + CU_ASSERT(binn_list_null(value.ptr, 2) == TRUE); + ptr = binn_list_str(value.ptr, 3); + CU_ASSERT(ptr != 0); + CU_ASSERT(strcmp((char*) ptr, "l1st2") == 0); + CU_ASSERT(binn_list_bool(value.ptr, 4) == TRUE); + CU_ASSERT(id == 55120); + + CU_ASSERT(binn_map_next(&iter, &id, &value) == FALSE); + //CU_ASSERT(iter.current == 13); + //CU_ASSERT(iter.count == 12); + + CU_ASSERT(binn_map_next(&iter, &id, &value) == FALSE); + //CU_ASSERT(iter.current == 13); // must keep the same position + //CU_ASSERT(iter.count == 12); + + + // test binn copy + + copy = binn_copy(list); + CU_ASSERT(copy != NULL); + CU_ASSERT(binn_type(copy) == binn_type(list)); + CU_ASSERT(binn_count(copy) == binn_count(list)); + CU_ASSERT(binn_size(copy) == binn_size(list)); + CU_ASSERT(binn_iter_init(&iter, list, BINN_LIST)); + CU_ASSERT(binn_iter_init(&iter2, copy, BINN_LIST)); + while (binn_list_next(&iter, &value)) { + CU_ASSERT(binn_list_next(&iter2, &value2) == TRUE); + CU_ASSERT(value.type == value2.type); + //CU_ASSERT(value.vint32 == value.vint32); + } + CU_ASSERT(binn_list_add_str(copy, "testing...") == TRUE); + CU_ASSERT(binn_type(copy) == binn_type(list)); + CU_ASSERT(binn_count(copy) == binn_count(list) + 1); + CU_ASSERT(binn_size(copy) > binn_size(list)); + binn_free(copy); + + copy = binn_copy(map); + CU_ASSERT(copy != NULL); + CU_ASSERT(binn_type(copy) == binn_type(map)); + CU_ASSERT(binn_count(copy) == binn_count(map)); + CU_ASSERT(binn_size(copy) == binn_size(map)); + CU_ASSERT(binn_iter_init(&iter, map, BINN_MAP)); + CU_ASSERT(binn_iter_init(&iter2, copy, BINN_MAP)); + while (binn_map_next(&iter, &id, &value)) { + CU_ASSERT(binn_map_next(&iter2, &id2, &value2) == TRUE); + CU_ASSERT(id == id2); + CU_ASSERT(value.type == value2.type); + //CU_ASSERT(value.vint32 == value.vint32); + } + CU_ASSERT(binn_map_set_int32(copy, 5600, 123) == TRUE); + CU_ASSERT(binn_type(copy) == binn_type(map)); + CU_ASSERT(binn_count(copy) == binn_count(map) + 1); + CU_ASSERT(binn_size(copy) > binn_size(map)); + binn_free(copy); + + copy = binn_copy(obj); + CU_ASSERT(copy != NULL); + CU_ASSERT(binn_type(copy) == binn_type(obj)); + CU_ASSERT(binn_count(copy) == binn_count(obj)); + CU_ASSERT(binn_size(copy) == binn_size(obj)); + CU_ASSERT(binn_iter_init(&iter, obj, BINN_OBJECT)); + CU_ASSERT(binn_iter_init(&iter2, copy, BINN_OBJECT)); + while (binn_object_next(&iter, key, &value)) { + CU_ASSERT(binn_object_next(&iter2, key2, &value2) == TRUE); + CU_ASSERT(strcmp(key, key2) == 0); + CU_ASSERT(value.type == value2.type); + //CU_ASSERT(value.vint32 == value.vint32); + } + CU_ASSERT(binn_object_set_int32(copy, "another", 123) == TRUE); + CU_ASSERT(binn_type(copy) == binn_type(obj)); + CU_ASSERT(binn_count(copy) == binn_count(obj) + 1); + CU_ASSERT(binn_size(copy) > binn_size(obj)); + binn_free(copy); + + + // test binn copy reading from buffer + + ptr = binn_ptr(list); + copy = binn_copy(ptr); + CU_ASSERT(copy != NULL); + CU_ASSERT(binn_type(copy) == binn_type(list)); + CU_ASSERT(binn_count(copy) == binn_count(list)); + CU_ASSERT(binn_size(copy) == binn_size(list)); + CU_ASSERT(binn_iter_init(&iter, ptr, BINN_LIST)); + CU_ASSERT(binn_iter_init(&iter2, copy, BINN_LIST)); + while (binn_list_next(&iter, &value)) { + CU_ASSERT(binn_list_next(&iter2, &value2) == TRUE); + CU_ASSERT(value.type == value2.type); + //CU_ASSERT(value.vint32 == value.vint32); + } + CU_ASSERT(binn_list_add_str(copy, "testing...") == TRUE); + CU_ASSERT(binn_type(copy) == binn_type(list)); + CU_ASSERT(binn_count(copy) == binn_count(list) + 1); + CU_ASSERT(binn_size(copy) > binn_size(list)); + binn_free(copy); + + ptr = binn_ptr(map); + copy = binn_copy(ptr); + CU_ASSERT(copy != NULL); + CU_ASSERT(binn_type(copy) == binn_type(map)); + CU_ASSERT(binn_count(copy) == binn_count(map)); + CU_ASSERT(binn_size(copy) == binn_size(map)); + CU_ASSERT(binn_iter_init(&iter, ptr, BINN_MAP)); + CU_ASSERT(binn_iter_init(&iter2, copy, BINN_MAP)); + while (binn_map_next(&iter, &id, &value)) { + CU_ASSERT(binn_map_next(&iter2, &id2, &value2) == TRUE); + CU_ASSERT(id == id2); + CU_ASSERT(value.type == value2.type); + //CU_ASSERT(value.vint32 == value.vint32); + } + CU_ASSERT(binn_map_set_int32(copy, 5600, 123) == TRUE); + CU_ASSERT(binn_type(copy) == binn_type(map)); + CU_ASSERT(binn_count(copy) == binn_count(map) + 1); + CU_ASSERT(binn_size(copy) > binn_size(map)); + binn_free(copy); + + ptr = binn_ptr(obj); + copy = binn_copy(ptr); + CU_ASSERT(copy != NULL); + CU_ASSERT(binn_type(copy) == binn_type(obj)); + CU_ASSERT(binn_count(copy) == binn_count(obj)); + CU_ASSERT(binn_size(copy) == binn_size(obj)); + CU_ASSERT(binn_iter_init(&iter, ptr, BINN_OBJECT)); + CU_ASSERT(binn_iter_init(&iter2, copy, BINN_OBJECT)); + while (binn_object_next(&iter, key, &value)) { + CU_ASSERT(binn_object_next(&iter2, key2, &value2) == TRUE); + CU_ASSERT(strcmp(key, key2) == 0); + CU_ASSERT(value.type == value2.type); + //CU_ASSERT(value.vint32 == value.vint32); + } + CU_ASSERT(binn_object_set_int32(copy, "another", 123) == TRUE); + CU_ASSERT(binn_type(copy) == binn_type(obj)); + CU_ASSERT(binn_count(copy) == binn_count(obj) + 1); + CU_ASSERT(binn_size(copy) > binn_size(obj)); + binn_free(copy); + + + binn_free(list); + binn_free(list2); + binn_free(map); + binn_free(obj); + + puts("OK"); +} + +/*************************************************************************************/ + +void test_binn2() { + char *obj1ptr, *obj2ptr; + int obj1size, obj2size; + + test_virtual_types(); + + test_int_conversion(); + test_binn_int_conversion(); + test_value_conversion(); + test_value_copy(); + + init_udts(); + + obj1ptr = test_create_object_1(&obj1size); + obj2ptr = test_create_object_2(&obj2size); + + CU_ASSERT(obj1ptr != NULL); + CU_ASSERT(obj2ptr != NULL); + + printf("obj1size=%d obj2size=%d\n", obj1size, obj2size); + CU_ASSERT(obj1size == obj2size); + + test_binn_read(obj1ptr); + + free(obj1ptr); + free(obj2ptr); + + test_binn_iter(); +} + +/*************************************************************************************/ + +int main() { + CU_pSuite pSuite = NULL; + if (CUE_SUCCESS != CU_initialize_registry()) { + return CU_get_error(); + } + pSuite = CU_add_suite("jbl_test_binn2", init_suite, clean_suite); + if (NULL == pSuite) { + CU_cleanup_registry(); + return CU_get_error(); + } + if ( + (NULL == CU_add_test(pSuite, "test_binn2", test_binn2))) { + CU_cleanup_registry(); + return CU_get_error(); + } + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + int ret = CU_get_error() || CU_get_number_of_failures(); + CU_cleanup_registry(); + return ret; +} diff --git a/src/jbr/README.md b/src/jbr/README.md new file mode 100644 index 0000000..e706c8d --- /dev/null +++ b/src/jbr/README.md @@ -0,0 +1,298 @@ +# HTTP REST/Websocket API endpoint + +EJDB engine provides the ability to start a separate HTTP/Websocket endpoint worker exposing network API for quering and data modifications. + +The easiest way to expose database over the network is using the standalone `jbs` server. (Of course if you plan to avoid `C API` integration). + +## jbs server + +``` +jbs -h + +EJDB 2.0.0 standalone REST/Websocket server. http://ejdb.org + + --file <> Database file path. Default: db.jb + -f <> (same as --file) + --port ## HTTP port number listen to. Default: 9191 + -p ## (same as --port) + --bind <> Address server listen. Default: localhost + -b <> (same as --bind) + --access <> Server access token matched to 'X-Access-Token' HTTP header value + -a <> (same as --access) + --trunc Cleanup existing database file on open + -t (same as --trunc) + --wal Use write ahead logging (WAL). Must be set for data durability. + -w (same as --wal) + +Advanced options + --sbz ## Max sorting buffer size. If exceeded, an overflow temp file for data will be created. Default: 16777216, min: 1048576 + --dsz ## Initial size of buffer to process/store document on queries. Preferable average size of document. Default: 65536, min: 16384 + --bsz ## Max HTTP/WS API document body size. Default: 67108864, min: 524288 + +Use any of the following input formats: + -arg -arg= -arg + +Use the -h, -help or -? to get this information again. +``` + +## HTTP API + +Access to HTTP endpoint can be protected by a token specified with `--access` +command flag or by C API `EJDB_HTTP` options. If access token specified on server, client must provide `X-Access-Token` HTTP header value. If token is required and not provided by client the `401` HTTP code will be reported. If access token is not matched to the token provided the `403` HTTP code will be returned. +For any other errors server will respond with `500` error code. + +## REST API + +### POST /{collection} +Add a new document to the `collection`. +* `200` success. Body: a new document identifier as `int64` number + +### PUT /{collection}/{id} +Replaces/store document under specific numeric `id` +* `200` on success. Empty body + +### DELETE /{collection}/{id} +Removes document identified by `id` from a `collection` +* `200` on success. Empty body +* `404` if document not found + +### PATCH /{collection}/{id} +Patch a document identified by `id` by [rfc7396](https://tools.ietf.org/html/rfc7396), +[rfc6902](https://tools.ietf.org/html/rfc6902) data. +* `200` on success. Empty body + +### GET | HEAD /{collections}/{id} +Retrieve document identified by `id` from a `collection`. +* `200` on success. Body: JSON document text. + * `content-type:application/json` + * `content-length:` +* `404` if document not found + +### POST / +Query a collection by provided query as POST body. +Body of query should contains collection name in use in the first filter element: `@collection_name/...` +Request headers: +* `X-Hints` comma separated extra hints to ejdb2 database engine. + * `explain` Show query execution plan before first element in result set separated by `--------------------` line. +Response: +* Response data transfered using [HTTP chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) +* `200` on success. +* JSON documents separated by `\n` in the following format: + ``` + \r\n\t + ... + ``` + +Example: + +``` +curl -v --data-raw '@family/[age > 18]' -H 'X-Access-Token:myaccess01' http://localhost:9191 +* Rebuilt URL to: http://localhost:9191/ +* Trying 127.0.0.1... +* TCP_NODELAY set +* Connected to localhost (127.0.0.1) port 9191 (#0) +> POST / HTTP/1.1 +> Host: localhost:9191 +> User-Agent: curl/7.58.0 +> Accept: */* +> X-Access-Token:myaccess01 +> Content-Length: 18 +> Content-Type: application/x-www-form-urlencoded +> +* upload completely sent off: 18 out of 18 bytes +< HTTP/1.1 200 OK +< connection:keep-alive +< content-type:application/json +< transfer-encoding:chunked +< + +4 {"firstName":"John","lastName":"Ryan","age":39} +3 {"firstName":"Jack","lastName":"Parker","age":35,"pets":[{"name":"Sonic","kind":"mouse","likes":[]}]} +1 {"firstName":"John","lastName":"Doe","age":28,"pets":[{"name":"Rexy rex","kind":"dog","likes":["bones","jumping","toys"]},{"name":"Grenny","kind":"parrot","likes":["green color","night","toys"]}],"address":{"city":"New York","street":"Fifth Avenue"}} +* Connection #0 to host localhost left intact +``` + +``` +curl --data-raw '@family/[lastName = "Ryan"]' -H 'X-Access-Token:myaccess01' -H 'X-Hints:explain' http://localhost:9191 +[INDEX] MATCHED STR|3 /lastName EXPR1: 'lastName = "Ryan"' INIT: IWKV_CURSOR_EQ +[INDEX] SELECTED STR|3 /lastName EXPR1: 'lastName = "Ryan"' INIT: IWKV_CURSOR_EQ + [COLLECTOR] PLAIN +-------------------- +4 {"firstName":"John","lastName":"Ryan","age":39} +``` + +### OPTIONS / +Fetch ejdb JSON metadata and available HTTP methods in `Allow` response header. +Example: +``` +curl -X OPTIONS -H 'X-Access-Token:myaccess01' http://localhost:9191/ +{ + "version": "2.0.0", + "file": "db.jb", + "size": 16384, + "collections": [ + { + "name": "family", + "dbid": 3, + "rnum": 3, + "indexes": [ + { + "ptr": "/lastName", + "mode": 4, + "idbf": 64, + "dbid": 4, + "rnum": 3 + } + ] + } + ] +} +``` + +## Websocket API + +EJDB supports simple text based protocol over HTTP websocket protocol. +You can use interactive websocket CLI tool [wscat](https://www.npmjs.com/package/@softmotions/wscat) to communicate with server by hands. + +### Commands + +#### ? +Will respond with the following help text message: +``` +wscat -H 'X-Access-Token:myaccess01' -c http://localhost:9191 +> ? +< + info + get + set + add + del + patch + idx + rmi + rmc + query + explain + +> +``` + +Note about `` prefix before every command; It is an arbitrary key chosen by client and designated to identify particular websocket request, this key will be returned with response to request and allows client to identify that response for his particular request. + +Errors are returned in the following format: +``` + ERROR: +``` + +#### ` info` +Get database metadatas as JSON document. + +#### ` get ` +Retrieve document identified by `id` from a `collection`. +If document is not found `IWKV_ERROR_NOTFOUND` will be returned. + +Example: +``` +> k get family 3 +< k 3 { + "firstName": "Jack", + "lastName": "Parker", + "age": 35, + "pets": [ + { + "name": "Sonic", + "kind": "mouse", + "likes": [] + } + ] +} +``` +If document not found we will get error: +``` +> k get family 55 +< k ERROR: Key not found. (IWKV_ERROR_NOTFOUND) +> +``` + +#### ` set ` +Replaces/add document under specific numeric `id`. +`Collection` will be created automatically if not exists. + +#### ` add ` +Add new document to `` New `id` of document will be generated +and returned as response. `Collection> will be created automatically if not exists. + +Example: +``` +> k add mycollection {"foo":"bar"} +< k 1 +> k add mycollection {"foo":"bar"} +< k 2 +> +``` + +#### ` del ` +Remove document identified by `id` from the `collection`. +If document is not found `IWKV_ERROR_NOTFOUND` will be returned. + +#### ` patch ` +Apply [rfc7396](https://tools.ietf.org/html/rfc7396) or +[rfc6902](https://tools.ietf.org/html/rfc6902) patch to the document identified by `id`. +If document is not found `IWKV_ERROR_NOTFOUND` will be returned. + +#### ` query ` +Execute query on documents in specified `collection`. +**Response:** A set of WS messages with document boidies terminated by the last +message with empty body. +``` +> k query family /* | /firstName +< k 4 {"firstName":"John"} +< k 3 {"firstName":"Jack"} +< k 1 {"firstName":"John"} +< k +``` +Note about last message: `` with no body. + +#### ` explain ` +Same as ` query ` but the first response message will +be prefixed by ` explain` and contains query execution plan. + +Example: +``` +> k explain family /* | /firstName +< k explain [INDEX] NO [COLLECTOR] PLAIN + +< k 4 {"firstName":"John"} +< k 3 {"firstName":"Jack"} +< k 1 {"firstName":"John"} +< k +``` + +#### +Execute query text. Body of query should contains collection name in use in the first filter element: `@collection_name/...`. Behavior is the same as for: ` query ` + +#### ` idx ` +Ensure index with specified `mode` (bitmask flag) for given json `path` and `collection`. +Collection will be created if not exists. + +Index mode | Description +--- | --- +0x01 EJDB_IDX_UNIQUE | Index is unique +0x04 EJDB_IDX_STR | Index for JSON `string` field value type +0x08 EJDB_IDX_I64 | Index for `8 bytes width` signed integer field values +0x10 EJDB_IDX_F64 | Index for `8 bytes width` signed floating point field values. + +##### Example +Set unique string index `(0x01 & 0x04) = 5` on `/name` JSON field: +``` +k idx mycollection 5 /name +``` + +#### ` rmi ` +Remove index with specified `mode` (bitmask flag) for given json `path` and `collection`. +Return error if given index not found. + +#### ` rmc ` +Remove collection and all of its data. +Note: If `collection` is not found no errors will be reported. + diff --git a/src/jbr/jbr.c b/src/jbr/jbr.c new file mode 100644 index 0000000..ec5ef87 --- /dev/null +++ b/src/jbr/jbr.c @@ -0,0 +1,1441 @@ +#include "jbr.h" +#include +#include +#include "ejdb2_internal.h" + +#define FIO_INCLUDE_STR + +#include +#include +#include +#include +#include + +#define JBR_MAX_KEY_LEN 36 +#define JBR_HTTP_CHUNK_SIZE 4096 +#define JBR_WS_STR_PREMATURE_END "Premature end of message" + +static uint64_t k_header_x_access_token_hash; +static uint64_t k_header_x_hints_hash; +static uint64_t k_header_content_length_hash; +static uint64_t k_header_content_type_hash; + +typedef enum { + JBR_GET = 1, + JBR_PUT, + JBR_PATCH, + JBR_DELETE, + JBR_POST, + JBR_HEAD, + JBR_OPTIONS, +} jbr_method_t; + +struct _JBR { + volatile bool terminated; + volatile iwrc rc; + pthread_t worker_thread; + pthread_barrier_t start_barrier; + const EJDB_HTTP *http; + EJDB db; +}; + +typedef struct _JBRCTX { + JBR jbr; + http_s *req; + const char *collection; + IWXSTR *wbuf; + int64_t id; + size_t collection_len; + jbr_method_t method; + bool read_anon; + bool data_sent; +} JBRCTX; + +#define JBR_RC_REPORT(code_, r_, rc_) \ + do { \ + if ((code_) >= 500) iwlog_ecode_error3(rc_); \ + const char *strerr = iwlog_ecode_explained(rc_); \ + _jbr_http_send(r_, code_, "text/plain", strerr, strerr ? strlen(strerr) : 0); \ + } while (0) + +IW_INLINE void _jbr_http_set_content_length(http_s *r, uintptr_t length) { + if (!fiobj_hash_get2(r->private_data.out_headers, k_header_content_length_hash)) { + fiobj_hash_set(r->private_data.out_headers, HTTP_HEADER_CONTENT_LENGTH, + fiobj_num_new(length)); + } +} + +IW_INLINE void _jbr_http_set_content_type(http_s *r, const char *ctype) { + if (!fiobj_hash_get2(r->private_data.out_headers, k_header_content_type_hash)) { + fiobj_hash_set(r->private_data.out_headers, HTTP_HEADER_CONTENT_TYPE, + fiobj_str_new(ctype, strlen(ctype))); + } +} + +IW_INLINE void _jbr_http_set_header( + http_s *r, + char *name, size_t nlen, + char *val, size_t vlen) { + http_set_header2(r, (fio_str_info_s) { + .data = name, .len = nlen + }, (fio_str_info_s) { + .data = val, .len = vlen + }); +} + +static iwrc _jbr_http_send(http_s *r, int status, const char *ctype, const char *body, int bodylen) { + if (!r || !r->private_data.out_headers) { + iwlog_ecode_error3(IW_ERROR_INVALID_ARGS); + return IW_ERROR_INVALID_ARGS; + } + r->status = status; + if (ctype) { + _jbr_http_set_content_type(r, ctype); + } + if (http_send_body(r, (char*) body, bodylen)) { + iwlog_ecode_error3(JBR_ERROR_SEND_RESPONSE); + return JBR_ERROR_SEND_RESPONSE; + } + return 0; +} + +IW_INLINE iwrc _jbr_http_error_send(http_s *r, int status) { + return _jbr_http_send(r, status, 0, 0, 0); +} + +IW_INLINE iwrc _jbr_http_error_send2(http_s *r, int status, const char *ctype, const char *body, int bodylen) { + return _jbr_http_send(r, status, ctype, body, bodylen); +} + +static iwrc _jbr_flush_chunk(JBRCTX *rctx, bool finish) { + http_s *req = rctx->req; + IWXSTR *wbuf = rctx->wbuf; + char nbuf[JBNUMBUF_SIZE + 2]; // + \r\n + assert(wbuf); + if (!rctx->data_sent) { + req->status = 200; + _jbr_http_set_content_type(req, "application/json"); + _jbr_http_set_header(req, "transfer-encoding", 17, "chunked", 7); + if (http_write_headers(req) < 0) { + iwlog_ecode_error3(JBR_ERROR_SEND_RESPONSE); + return JBR_ERROR_SEND_RESPONSE; + } + rctx->data_sent = true; + } + if (!finish && (iwxstr_size(wbuf) < JBR_HTTP_CHUNK_SIZE)) { + return 0; + } + intptr_t uuid = http_uuid(req); + if (iwxstr_size(wbuf) > 0) { + int sz = snprintf(nbuf, JBNUMBUF_SIZE, "%zX\r\n", iwxstr_size(wbuf)); + if (fio_write(uuid, nbuf, sz) < 0) { + iwlog_ecode_error3(JBR_ERROR_SEND_RESPONSE); + return JBR_ERROR_SEND_RESPONSE; + } + if (fio_write(uuid, iwxstr_ptr(wbuf), iwxstr_size(wbuf)) < 0) { + iwlog_ecode_error3(JBR_ERROR_SEND_RESPONSE); + return JBR_ERROR_SEND_RESPONSE; + } + if (fio_write(uuid, "\r\n", 2) < 0) { + iwlog_ecode_error3(JBR_ERROR_SEND_RESPONSE); + return JBR_ERROR_SEND_RESPONSE; + } + iwxstr_clear(wbuf); + } + if (finish) { + if (fio_write(uuid, "0\r\n\r\n", 5) < 0) { + iwlog_ecode_error3(JBR_ERROR_SEND_RESPONSE); + return JBR_ERROR_SEND_RESPONSE; + } + } + return 0; +} + +static iwrc _jbr_query_visitor(EJDB_EXEC *ux, EJDB_DOC doc, int64_t *step) { + JBRCTX *rctx = ux->opaque; + assert(rctx); + iwrc rc = 0; + IWXSTR *wbuf = rctx->wbuf; + if (!wbuf) { + wbuf = iwxstr_new2(512); + if (!wbuf) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + rctx->wbuf = wbuf; + } + if (ux->log) { + rc = iwxstr_cat(wbuf, iwxstr_ptr(ux->log), iwxstr_size(ux->log)); + RCRET(rc); + rc = iwxstr_cat(wbuf, "--------------------", 20); + RCRET(rc); + iwxstr_destroy(ux->log); + ux->log = 0; + } + rc = iwxstr_printf(wbuf, "\r\n%lld\t", doc->id); + RCRET(rc); + if (doc->node) { + rc = jbn_as_json(doc->node, jbl_xstr_json_printer, wbuf, 0); + } else { + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, wbuf, 0); + } + RCRET(rc); + return _jbr_flush_chunk(rctx, false); +} + +static void _jbr_on_query(JBRCTX *rctx) { + http_s *req = rctx->req; + fio_str_info_s data = fiobj_data_read(req->body, 0); + if (data.len < 1) { + _jbr_http_error_send(rctx->req, 400); + return; + } + EJDB_EXEC ux = { + .opaque = rctx, + .db = rctx->jbr->db, + .visitor = _jbr_query_visitor + }; + + // Collection name must be encoded in query + iwrc rc = jql_create2(&ux.q, 0, data.data, JQL_SILENT_ON_PARSE_ERROR | JQL_KEEP_QUERY_ON_PARSE_ERROR); + RCGO(rc, finish); + if (rctx->read_anon && jql_has_apply(ux.q)) { + // We have not permitted data modification request + jql_destroy(&ux.q); + _jbr_http_error_send(rctx->req, 403); + return; + } + + FIOBJ h = fiobj_hash_get2(req->headers, k_header_x_hints_hash); + if (h) { + if (!fiobj_type_is(h, FIOBJ_T_STRING)) { + jql_destroy(&ux.q); + _jbr_http_error_send(req, 400); + return; + } + fio_str_info_s hv = fiobj_obj2cstr(h); + if (strstr(hv.data, "explain")) { + ux.log = iwxstr_new(); + if (!ux.log) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + } + } + + rc = ejdb_exec(&ux); + + if (!rc && rctx->wbuf) { + rc = iwxstr_cat(rctx->wbuf, "\r\n", 2); + RCGO(rc, finish); + rc = _jbr_flush_chunk(rctx, true); + } + +finish: + if (rc) { + iwrc rcs = rc; + iwrc_strip_code(&rcs); + switch (rcs) { + case JQL_ERROR_QUERY_PARSE: { + const char *err = jql_error(ux.q); + _jbr_http_error_send2(rctx->req, 400, "text/plain", err, err ? (int) strlen(err) : 0); + break; + } + case JQL_ERROR_NO_COLLECTION: + JBR_RC_REPORT(400, req, rc); + break; + default: + if (rctx->data_sent) { + // We cannot report error over HTTP + // because already sent some data to client + iwlog_ecode_error3(rc); + http_complete(req); + } else { + JBR_RC_REPORT(500, req, rc); + } + break; + } + } else if (rctx->data_sent) { + http_complete(req); + } else if (ux.log) { + iwxstr_cat(ux.log, "--------------------", 20); + if (jql_has_aggregate_count(ux.q)) { + iwxstr_printf(ux.log, "\n%lld", ux.cnt); + } + _jbr_http_send(req, 200, "text/plain", iwxstr_ptr(ux.log), iwxstr_size(ux.log)); + } else { + if (jql_has_aggregate_count(ux.q)) { + char nbuf[JBNUMBUF_SIZE]; + snprintf(nbuf, sizeof(nbuf), "%" PRId64, ux.cnt); + _jbr_http_send(req, 200, "text/plain", nbuf, (int) strlen(nbuf)); + } else { + _jbr_http_send(req, 200, 0, 0, 0); + } + } + if (ux.q) { + jql_destroy(&ux.q); + } + if (ux.log) { + iwxstr_destroy(ux.log); + } + if (rctx->wbuf) { + iwxstr_destroy(rctx->wbuf); + rctx->wbuf = 0; + } +} + +static void _jbr_on_patch(JBRCTX *rctx) { + if (rctx->read_anon) { + _jbr_http_error_send(rctx->req, 403); + return; + } + EJDB db = rctx->jbr->db; + http_s *req = rctx->req; + fio_str_info_s data = fiobj_data_read(req->body, 0); + if (data.len < 1) { + _jbr_http_error_send(rctx->req, 400); + return; + } + iwrc rc = ejdb_patch(db, rctx->collection, data.data, rctx->id); + iwrc_strip_code(&rc); + switch (rc) { + case IWKV_ERROR_NOTFOUND: + case IW_ERROR_NOT_EXISTS: + _jbr_http_error_send(req, 404); + return; + case JBL_ERROR_PARSE_JSON: + case JBL_ERROR_PARSE_INVALID_CODEPOINT: + case JBL_ERROR_PARSE_INVALID_UTF8: + case JBL_ERROR_PARSE_UNQUOTED_STRING: + case JBL_ERROR_PATCH_TARGET_INVALID: + case JBL_ERROR_PATCH_NOVALUE: + case JBL_ERROR_PATCH_INVALID_OP: + case JBL_ERROR_PATCH_TEST_FAILED: + case JBL_ERROR_PATCH_INVALID_ARRAY_INDEX: + case JBL_ERROR_JSON_POINTER: + JBR_RC_REPORT(400, req, rc); + break; + default: + break; + } + if (rc) { + JBR_RC_REPORT(500, req, rc); + } else { + _jbr_http_send(req, 200, 0, 0, 0); + } +} + +static void _jbr_on_delete(JBRCTX *rctx) { + if (rctx->read_anon) { + _jbr_http_error_send(rctx->req, 403); + return; + } + EJDB db = rctx->jbr->db; + http_s *req = rctx->req; + iwrc rc = ejdb_del(db, rctx->collection, rctx->id); + if ((rc == IWKV_ERROR_NOTFOUND) || (rc == IW_ERROR_NOT_EXISTS)) { + _jbr_http_error_send(req, 404); + return; + } else if (rc) { + JBR_RC_REPORT(500, req, rc); + return; + } + _jbr_http_send(req, 200, 0, 0, 0); +} + +static void _jbr_on_put(JBRCTX *rctx) { + if (rctx->read_anon) { + _jbr_http_error_send(rctx->req, 403); + return; + } + JBL jbl; + EJDB db = rctx->jbr->db; + http_s *req = rctx->req; + fio_str_info_s data = fiobj_data_read(req->body, 0); + if (data.len < 1) { + _jbr_http_error_send(rctx->req, 400); + return; + } + iwrc rc = jbl_from_json(&jbl, data.data); + if (rc) { + JBR_RC_REPORT(400, req, rc); + return; + } + rc = ejdb_put(db, rctx->collection, jbl, rctx->id); + if (rc) { + JBR_RC_REPORT(500, req, rc); + goto finish; + } + _jbr_http_send(req, 200, 0, 0, 0); + +finish: + jbl_destroy(&jbl); +} + +static void _jbr_on_post(JBRCTX *rctx) { + if (rctx->read_anon) { + _jbr_http_error_send(rctx->req, 403); + return; + } + JBL jbl; + int64_t id; + EJDB db = rctx->jbr->db; + http_s *req = rctx->req; + fio_str_info_s data = fiobj_data_read(req->body, 0); + if (data.len < 1) { + _jbr_http_error_send(rctx->req, 400); + return; + } + iwrc rc = jbl_from_json(&jbl, data.data); + if (rc) { + JBR_RC_REPORT(400, req, rc); + return; + } + rc = ejdb_put_new(db, rctx->collection, jbl, &id); + if (rc) { + JBR_RC_REPORT(500, req, rc); + goto finish; + } + + char nbuf[JBNUMBUF_SIZE]; + int len = iwitoa(id, nbuf, sizeof(nbuf)); + _jbr_http_send(req, 200, "text/plain", nbuf, len); + +finish: + jbl_destroy(&jbl); +} + +static void _jbr_on_get(JBRCTX *rctx) { + JBL jbl; + IWXSTR *xstr = 0; + int nbytes = 0; + EJDB db = rctx->jbr->db; + http_s *req = rctx->req; + + iwrc rc = ejdb_get(db, rctx->collection, rctx->id, &jbl); + if ((rc == IWKV_ERROR_NOTFOUND) || (rc == IW_ERROR_NOT_EXISTS)) { + _jbr_http_error_send(req, 404); + return; + } else if (rc) { + JBR_RC_REPORT(500, req, rc); + return; + } + if (req->method == JBR_HEAD) { + rc = jbl_as_json(jbl, jbl_count_json_printer, &nbytes, JBL_PRINT_PRETTY); + } else { + xstr = iwxstr_new2(jbl->bn.size * 2); + if (!xstr) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, JBL_PRINT_PRETTY); + } + RCGO(rc, finish); + + _jbr_http_send(req, 200, "application/json", + xstr ? iwxstr_ptr(xstr) : 0, + xstr ? (int) iwxstr_size(xstr) : nbytes); +finish: + if (rc) { + JBR_RC_REPORT(500, req, rc); + } + jbl_destroy(&jbl); + if (xstr) { + iwxstr_destroy(xstr); + } +} + +static void _jbr_on_options(JBRCTX *rctx) { + JBL jbl; + EJDB db = rctx->jbr->db; + http_s *req = rctx->req; + const EJDB_HTTP *http = rctx->jbr->http; + + iwrc rc = ejdb_get_meta(db, &jbl); + if (rc) { + JBR_RC_REPORT(500, req, rc); + return; + } + IWXSTR *xstr = iwxstr_new2(jbl->bn.size * 2); + if (!xstr) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, JBL_PRINT_PRETTY); + RCGO(rc, finish); + + if (http->read_anon) { + _jbr_http_set_header(req, "Allow", 5, "GET, HEAD, POST, OPTIONS", 24); + } else { + _jbr_http_set_header(req, "Allow", 5, "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS", 44); + } + + if (http->cors) { + _jbr_http_set_header(req, "Access-Control-Allow-Origin", 27, "*", 1); + _jbr_http_set_header(req, "Access-Control-Allow-Headers", 28, "X-Requested-With, Content-Type, Accept, Origin, Authorization", 61); + + if (http->read_anon) { + _jbr_http_set_header(req, "Access-Control-Allow-Methods", 28, "GET, HEAD, POST, OPTIONS", 24); + } else { + _jbr_http_set_header(req, "Access-Control-Allow-Methods", 28, "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS", 44); + } + } + + _jbr_http_send(req, 200, "application/json", + iwxstr_ptr(xstr), + iwxstr_size(xstr)); +finish: + if (rc) { + JBR_RC_REPORT(500, req, rc); + } + jbl_destroy(&jbl); + if (xstr) { + iwxstr_destroy(xstr); + } +} + +static bool _jbr_fill_ctx(http_s *req, JBRCTX *r) { + JBR jbr = req->udata; + memset(r, 0, sizeof(*r)); + r->req = req; + r->jbr = jbr; + fio_str_info_s method = fiobj_obj2cstr(req->method); + switch (method.len) { + case 3: + if (!strncmp("GET", method.data, method.len)) { + r->method = JBR_GET; + } else if (!strncmp("PUT", method.data, method.len)) { + r->method = JBR_PUT; + } + break; + case 4: + if (!strncmp("POST", method.data, method.len)) { + r->method = JBR_POST; + } else if (!strncmp("HEAD", method.data, method.len)) { + r->method = JBR_HEAD; + } + break; + case 5: + if (!strncmp("PATCH", method.data, method.len)) { + r->method = JBR_PATCH; + } + break; + case 6: + if (!strncmp("DELETE", method.data, method.len)) { + r->method = JBR_DELETE; + } + break; + case 7: + if (!strncmp("OPTIONS", method.data, method.len)) { + r->method = JBR_OPTIONS; + } + break; + } + if (!r->method) { + // Unknown method + return false; + } + fio_str_info_s path = fiobj_obj2cstr(req->path); + if (!req->path || (path.len < 2)) { + return true; + } else if (r->method == JBR_OPTIONS) { + return false; + } + + char *c = strchr(path.data + 1, '/'); + if (!c) { + switch (r->method) { + case JBR_GET: + case JBR_HEAD: + case JBR_PUT: + case JBR_DELETE: + case JBR_PATCH: + return false; + default: + break; + } + r->collection = path.data + 1; + r->collection_len = path.len - 1; + } else { + char *eptr; + char nbuf[JBNUMBUF_SIZE]; + r->collection = path.data + 1; + r->collection_len = c - r->collection; + int nlen = path.len - (c - path.data) - 1; + if (nlen < 1) { + goto finish; + } + if (nlen > JBNUMBUF_SIZE - 1) { + return false; + } + memcpy(nbuf, r->collection + r->collection_len + 1, nlen); + nbuf[nlen] = '\0'; + r->id = strtoll(nbuf, &eptr, 10); + if ((*eptr != '\0') || (r->id < 1) || (r->method == JBR_POST)) { + return false; + } + } + +finish: + return (r->collection_len <= EJDB_COLLECTION_NAME_MAX_LEN); +} + +static void _jbr_on_http_request(http_s *req) { + JBRCTX rctx; + JBR jbr = req->udata; + assert(jbr); + const EJDB_HTTP *http = jbr->http; + char cname[EJDB_COLLECTION_NAME_MAX_LEN + 1]; + + if (http->cors) { + _jbr_http_set_header(req, "Access-Control-Allow-Origin", 27, "*", 1); + } + + if (!_jbr_fill_ctx(req, &rctx)) { + http_send_error(req, 400); // Bad request + return; + } + if (http->access_token) { + FIOBJ h = fiobj_hash_get2(req->headers, k_header_x_access_token_hash); + if (!h) { + if (http->read_anon) { + if ((rctx.method == JBR_GET) || (rctx.method == JBR_HEAD) || ((rctx.method == JBR_POST) && !rctx.collection)) { + rctx.read_anon = true; + goto process; + } + } + http_send_error(req, 401); + return; + } + if (!fiobj_type_is(h, FIOBJ_T_STRING)) { // header specified more than once + http_send_error(req, 400); + return; + } + fio_str_info_s hv = fiobj_obj2cstr(h); + if ((hv.len != http->access_token_len) || (memcmp(hv.data, http->access_token, http->access_token_len) != 0)) { // -V526 + http_send_error(req, 403); + return; + } + } + +process: + if (rctx.collection) { + // convert to `\0` terminated c-string + memcpy(cname, rctx.collection, rctx.collection_len); + cname[rctx.collection_len] = '\0'; + rctx.collection = cname; + switch (rctx.method) { + case JBR_GET: + case JBR_HEAD: + _jbr_on_get(&rctx); + break; + case JBR_POST: + _jbr_on_post(&rctx); + break; + case JBR_PUT: + _jbr_on_put(&rctx); + break; + case JBR_PATCH: + _jbr_on_patch(&rctx); + break; + case JBR_DELETE: + _jbr_on_delete(&rctx); + break; + default: + http_send_error(req, 400); + break; + } + } else if (rctx.method == JBR_POST) { + _jbr_on_query(&rctx); + } else if (rctx.method == JBR_OPTIONS) { + _jbr_on_options(&rctx); + } else { + http_send_error(req, 400); + } +} + +static void _jbr_on_http_finish(struct http_settings_s *settings) { +} + +static void _jbr_on_pre_start(void *op) { + JBR jbr = op; + if (!jbr->http->blocking) { + pthread_barrier_wait(&jbr->start_barrier); + } +} + +//------------------ WS --------------------- + + +#define _WS_KEYPREFIX_BUFSZ (JBNUMBUF_SIZE + JBR_MAX_KEY_LEN + 2) + +typedef enum { + JBWS_NONE, + JBWS_SET, + JBWS_GET, + JBWS_ADD, + JBWS_DEL, + JBWS_PATCH, + JBWS_QUERY, + JBWS_EXPLAIN, + JBWS_INFO, + JBWS_IDX, + JBWS_NIDX, + JBWS_REMOVE_COLL, +} jbwsop_t; + +typedef struct _JBWCTX { + bool read_anon; + EJDB db; + ws_s *ws; +} JBWCTX; + +IW_INLINE bool _jbr_ws_write_text(ws_s *ws, const char *data, int len) { + if (fio_is_closed(websocket_uuid(ws)) || (websocket_write(ws, (fio_str_info_s) { + .data = (char*) data, .len = len + }, 1) < 0)) { + iwlog_warn2("Websocket channel closed"); + return false; + } + return true; +} + +IW_INLINE int _jbr_fill_prefix_buf(const char *key, int64_t id, char buf[static _WS_KEYPREFIX_BUFSZ]) { + int len = (int) strlen(key); + char *wp = buf; + memcpy(wp, key, len); // NOLINT(bugprone-not-null-terminated-result) + wp += len; + *wp++ = '\t'; + wp += iwitoa(id, wp, _WS_KEYPREFIX_BUFSZ - (wp - buf)); + return (int) (wp - buf); +} + +static void _jbr_ws_on_open(ws_s *ws) { + JBWCTX *wctx = websocket_udata_get(ws); + if (wctx) { + wctx->ws = ws; + } +} + +static void _jbr_ws_on_close(intptr_t uuid, void *udata) { + JBWCTX *wctx = udata; + free(wctx); +} + +static void _jbr_ws_send_error(JBWCTX *wctx, const char *key, const char *error, const char *extra) { + assert(wctx && key && error); + IWXSTR *xstr = iwxstr_new(); + if (!xstr) { + iwlog_ecode_error3(iwrc_set_errno(IW_ERROR_ALLOC, errno)); + return; + } + iwrc rc; + if (extra) { + rc = iwxstr_printf(xstr, "%s ERROR: %s %s", key, error, extra); + } else { + rc = iwxstr_printf(xstr, "%s ERROR: %s", key, error); + } + if (rc) { + iwlog_ecode_error3(rc); + } else { + _jbr_ws_write_text(wctx->ws, iwxstr_ptr(xstr), iwxstr_size(xstr)); + } + iwxstr_destroy(xstr); +} + +static void _jbr_ws_send_rc(JBWCTX *wctx, const char *key, iwrc rc, const char *extra) { + const char *error = iwlog_ecode_explained(rc); + if (error) { + _jbr_ws_send_error(wctx, key, error, extra); + } +} + +static void _jbr_ws_add_document(JBWCTX *wctx, const char *key, const char *coll, const char *json) { + if (wctx->read_anon) { + _jbr_ws_send_rc(wctx, key, JBR_ERROR_WS_ACCESS_DENIED, 0); + return; + } + JBL jbl; + int64_t id; + iwrc rc = jbl_from_json(&jbl, json); + if (rc) { + _jbr_ws_send_rc(wctx, key, rc, 0); + return; + } + rc = ejdb_put_new(wctx->db, coll, jbl, &id); + if (rc) { + _jbr_ws_send_rc(wctx, key, rc, 0); + goto finish; + } + char pbuf[_WS_KEYPREFIX_BUFSZ]; + _jbr_fill_prefix_buf(key, id, pbuf); + _jbr_ws_write_text(wctx->ws, pbuf, (int) strlen(pbuf)); + +finish: + jbl_destroy(&jbl); +} + +static void _jbr_ws_set_document(JBWCTX *wctx, const char *key, const char *coll, int64_t id, const char *json) { + if (wctx->read_anon) { + _jbr_ws_send_rc(wctx, key, JBR_ERROR_WS_ACCESS_DENIED, 0); + return; + } + JBL jbl; + iwrc rc = jbl_from_json(&jbl, json); + if (rc) { + _jbr_ws_send_rc(wctx, key, rc, 0); + return; + } + rc = ejdb_put(wctx->db, coll, jbl, id); + if (rc) { + _jbr_ws_send_rc(wctx, key, rc, 0); + goto finish; + } + char pbuf[_WS_KEYPREFIX_BUFSZ]; + int len = _jbr_fill_prefix_buf(key, id, pbuf); + _jbr_ws_write_text(wctx->ws, pbuf, len); + +finish: + jbl_destroy(&jbl); +} + +static void _jbr_ws_get_document(JBWCTX *wctx, const char *key, const char *coll, int64_t id) { + JBL jbl; + iwrc rc = ejdb_get(wctx->db, coll, id, &jbl); + if (rc) { + _jbr_ws_send_rc(wctx, key, rc, 0); + return; + } + IWXSTR *xstr = iwxstr_new2(jbl->bn.size * 2); + if (!xstr) { + rc = iwrc_set_errno(rc, IW_ERROR_ALLOC); + iwlog_ecode_error3(rc); + _jbr_ws_send_rc(wctx, key, rc, 0); + return; + } + char pbuf[_WS_KEYPREFIX_BUFSZ]; + _jbr_fill_prefix_buf(key, id, pbuf); + rc = iwxstr_printf(xstr, "%s\t", pbuf); + if (rc) { + _jbr_ws_send_rc(wctx, key, rc, 0); + goto finish; + } + rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, JBL_PRINT_PRETTY); + if (rc) { + _jbr_ws_send_rc(wctx, key, rc, 0); + goto finish; + } + _jbr_ws_write_text(wctx->ws, iwxstr_ptr(xstr), iwxstr_size(xstr)); + +finish: + iwxstr_destroy(xstr); + jbl_destroy(&jbl); +} + +static void _jbr_ws_del_document(JBWCTX *wctx, const char *key, const char *coll, int64_t id) { + iwrc rc = ejdb_del(wctx->db, coll, id); + if (rc) { + _jbr_ws_send_rc(wctx, key, rc, 0); + return; + } + char pbuf[_WS_KEYPREFIX_BUFSZ]; + int len = _jbr_fill_prefix_buf(key, id, pbuf); + _jbr_ws_write_text(wctx->ws, pbuf, len); +} + +static void _jbr_ws_patch_document(JBWCTX *wctx, const char *key, const char *coll, int64_t id, const char *json) { + if (wctx->read_anon) { + _jbr_ws_send_rc(wctx, key, JBR_ERROR_WS_ACCESS_DENIED, 0); + return; + } + iwrc rc = ejdb_patch(wctx->db, coll, json, id); + if (rc) { + _jbr_ws_send_rc(wctx, key, rc, 0); + return; + } + char pbuf[_WS_KEYPREFIX_BUFSZ]; + int len = _jbr_fill_prefix_buf(key, id, pbuf); + _jbr_ws_write_text(wctx->ws, pbuf, len); +} + +static void _jbr_ws_set_index(JBWCTX *wctx, const char *key, const char *coll, int64_t mode, const char *path) { + if (wctx->read_anon) { + _jbr_ws_send_rc(wctx, key, JBR_ERROR_WS_ACCESS_DENIED, 0); + return; + } + iwrc rc = ejdb_ensure_index(wctx->db, coll, path, mode); + if (rc) { + _jbr_ws_send_rc(wctx, key, rc, 0); + } else { + _jbr_ws_write_text(wctx->ws, key, (int) strlen(key)); + } +} + +static void _jbr_ws_del_index(JBWCTX *wctx, const char *key, const char *coll, int64_t mode, const char *path) { + if (wctx->read_anon) { + _jbr_ws_send_rc(wctx, key, JBR_ERROR_WS_ACCESS_DENIED, 0); + return; + } + iwrc rc = ejdb_remove_index(wctx->db, coll, path, mode); + if (rc) { + _jbr_ws_send_rc(wctx, key, rc, 0); + } else { + _jbr_ws_write_text(wctx->ws, key, (int) strlen(key)); + } +} + +typedef struct JBWQCTX { + JBWCTX *wctx; + IWXSTR *wbuf; + const char *key; +} JBWQCTX; + +static iwrc _jbr_ws_query_visitor(EJDB_EXEC *ux, EJDB_DOC doc, int64_t *step) { + iwrc rc = 0; + JBWQCTX *qctx = ux->opaque; + assert(qctx); + IWXSTR *wbuf = qctx->wbuf; + if (!wbuf) { + wbuf = iwxstr_new2(512); + if (!wbuf) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + qctx->wbuf = wbuf; + } else { + iwxstr_clear(wbuf); + } + if (ux->log) { + rc = iwxstr_printf(wbuf, "%s\texplain\t%s", qctx->key, iwxstr_ptr(ux->log)); + iwxstr_destroy(ux->log); + ux->log = 0; + RCRET(rc); + _jbr_ws_write_text(qctx->wctx->ws, iwxstr_ptr(wbuf), iwxstr_size(wbuf)); + iwxstr_clear(wbuf); + } + + rc = iwxstr_printf(wbuf, "%s\t%lld\t", qctx->key, doc->id); + RCRET(rc); + + if (doc->node) { + rc = jbn_as_json(doc->node, jbl_xstr_json_printer, wbuf, 0); + } else { + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, wbuf, 0); + } + RCRET(rc); + if (!_jbr_ws_write_text(qctx->wctx->ws, iwxstr_ptr(wbuf), iwxstr_size(wbuf))) { + *step = 0; + } + return 0; +} + +static void _jbr_ws_query(JBWCTX *wctx, const char *key, const char *coll, const char *query, bool explain) { + JBWQCTX qctx = { + .wctx = wctx, + .key = key + }; + EJDB_EXEC ux = { + .db = wctx->db, + .opaque = &qctx, + .visitor = _jbr_ws_query_visitor, + }; + + iwrc rc = jql_create2(&ux.q, coll, query, JQL_SILENT_ON_PARSE_ERROR | JQL_KEEP_QUERY_ON_PARSE_ERROR); + RCGO(rc, finish); + + if (wctx->read_anon && jql_has_apply(ux.q)) { + _jbr_ws_send_rc(wctx, key, JBR_ERROR_WS_ACCESS_DENIED, 0); + goto finish; + } + + if (explain) { + ux.log = iwxstr_new(); + if (!ux.log) { + iwlog_ecode_error3(iwrc_set_errno(IW_ERROR_ALLOC, errno)); + goto finish; + } + } + + rc = ejdb_exec(&ux); + + if (!rc) { + if (ux.log) { + IWXSTR *wbuf = iwxstr_new(); + if (!wbuf) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + if (!iwxstr_printf(wbuf, "%s\texplain\t%s", qctx.key, iwxstr_ptr(ux.log))) { + _jbr_ws_write_text(wctx->ws, iwxstr_ptr(wbuf), iwxstr_size(wbuf)); + } + iwxstr_destroy(wbuf); + } + } + +finish: + if (rc) { + iwrc rcs = rc; + iwrc_strip_code(&rcs); + switch (rcs) { + case JQL_ERROR_QUERY_PARSE: + _jbr_ws_send_error(wctx, key, jql_error(ux.q), 0); + break; + default: + _jbr_ws_send_rc(wctx, key, rc, 0); + break; + } + } else { + if (jql_has_aggregate_count(ux.q)) { + char pbuf[_WS_KEYPREFIX_BUFSZ]; + _jbr_fill_prefix_buf(key, ux.cnt, pbuf); + _jbr_ws_write_text(wctx->ws, pbuf, (int) strlen(pbuf)); + } + _jbr_ws_write_text(wctx->ws, key, (int) strlen(key)); + } + if (ux.q) { + jql_destroy(&ux.q); + } + if (ux.log) { + iwxstr_destroy(ux.log); + } + if (qctx.wbuf) { + iwxstr_destroy(qctx.wbuf); + } +} + +static void _jbr_ws_info(JBWCTX *wctx, const char *key) { + if (wctx->read_anon) { + _jbr_ws_send_rc(wctx, key, JBR_ERROR_WS_ACCESS_DENIED, 0); + return; + } + JBL jbl; + iwrc rc = ejdb_get_meta(wctx->db, &jbl); + if (rc) { + _jbr_ws_send_rc(wctx, key, rc, 0); + return; + } + IWXSTR *xstr = iwxstr_new2(jbl->bn.size * 2); + if (!xstr) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + RCGO(rc, finish); + } + rc = iwxstr_printf(xstr, "%s\t", key); + RCGO(rc, finish); + + rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, JBL_PRINT_PRETTY); + RCGO(rc, finish); + _jbr_ws_write_text(wctx->ws, iwxstr_ptr(xstr), iwxstr_size(xstr)); + +finish: + if (rc) { + _jbr_ws_send_rc(wctx, key, rc, 0); + } + jbl_destroy(&jbl); + if (xstr) { + iwxstr_destroy(xstr); + } +} + +static void _jbr_ws_remove_coll(JBWCTX *wctx, const char *key, const char *coll) { + if (wctx->read_anon) { + _jbr_ws_send_rc(wctx, key, JBR_ERROR_WS_ACCESS_DENIED, 0); + return; + } + iwrc rc = ejdb_remove_collection(wctx->db, coll); + if (rc) { + _jbr_ws_send_rc(wctx, key, rc, 0); + } else { + _jbr_ws_write_text(wctx->ws, key, (int) strlen(key)); + } +} + +static void _jbr_ws_on_message(ws_s *ws, fio_str_info_s msg, uint8_t is_text) { + if (!is_text) { // Do not serve binary requests + websocket_close(ws); + return; + } + if (!msg.data || (msg.len < 1)) { // Ignore empty messages, but keep connection + return; + } + JBWCTX *wctx = websocket_udata_get(ws); + assert(wctx); + wctx->ws = ws; + jbwsop_t wsop = JBWS_NONE; + + char keybuf[JBR_MAX_KEY_LEN + 1]; + char cnamebuf[EJDB_COLLECTION_NAME_MAX_LEN + 1]; + + char *data = msg.data, *key = 0; + int len = msg.len, pos; + + // Trim right + for (pos = len; pos > 0 && isspace(data[pos - 1]); --pos) ; + len = pos; + // Trim left + for (pos = 0; pos < len && isspace(data[pos]); ++pos) ; + len -= pos; + data += pos; + if (len < 1) { + return; + } + if ((len == 1) && (data[0] == '?')) { + const char *help + = "\n info" + "\n get " + "\n set " + "\n add " + "\n del " + "\n patch " + "\n idx " + "\n rmi " + "\n rmc " + "\n query " + "\n explain " + "\n " + "\n"; + _jbr_ws_write_text(ws, help, (int) strlen(help)); + return; + } + + // Fetch key, after we can do good errors reporting + for (pos = 0; pos < len && !isspace(data[pos]); ++pos) ; + if (pos > JBR_MAX_KEY_LEN) { + iwlog_warn("The key length: %d exceeded limit: %d", pos, JBR_MAX_KEY_LEN); + return; + } + memcpy(keybuf, data, pos); + keybuf[pos] = '\0'; + key = keybuf; + if (pos >= len) { + _jbr_ws_send_rc(wctx, key, JBR_ERROR_WS_INVALID_MESSAGE, JBR_WS_STR_PREMATURE_END); + return; + } + + // Space + for ( ; pos < len && isspace(data[pos]); ++pos) ; + len -= pos; + data += pos; + if (len < 1) { + _jbr_ws_send_rc(wctx, key, JBR_ERROR_WS_INVALID_MESSAGE, JBR_WS_STR_PREMATURE_END); + return; + } + + // Fetch command + for (pos = 0; pos < len && !isspace(data[pos]); ++pos) ; + + if (pos <= len) { + if (!strncmp("get", data, pos)) { + wsop = JBWS_GET; + } else if (!strncmp("add", data, pos)) { + wsop = JBWS_ADD; + } else if (!strncmp("set", data, pos)) { + wsop = JBWS_SET; + } else if (!strncmp("query", data, pos)) { + wsop = JBWS_QUERY; + } else if (!strncmp("del", data, pos)) { + wsop = JBWS_DEL; + } else if (!strncmp("patch", data, pos)) { + wsop = JBWS_PATCH; + } else if (!strncmp("explain", data, pos)) { + wsop = JBWS_EXPLAIN; + } else if (!strncmp("info", data, pos)) { + wsop = JBWS_INFO; + } else if (!strncmp("idx", data, pos)) { + wsop = JBWS_IDX; + } else if (!strncmp("rmi", data, pos)) { + wsop = JBWS_NIDX; + } else if (!strncmp("rmc", data, pos)) { + wsop = JBWS_REMOVE_COLL; + } + } + + if (wsop > JBWS_NONE) { + if (wsop == JBWS_INFO) { + _jbr_ws_info(wctx, key); + return; + } + for ( ; pos < len && isspace(data[pos]); ++pos) ; + len -= pos; + data += pos; + + char *coll = data; + for (pos = 0; pos < len && !isspace(data[pos]); ++pos) ; + len -= pos; + data += pos; + + if ((pos < 1) || (len < 1)) { + if (wsop != JBWS_REMOVE_COLL) { + _jbr_ws_send_rc(wctx, key, JBR_ERROR_WS_INVALID_MESSAGE, JBR_WS_STR_PREMATURE_END); + return; + } + } else if (pos > EJDB_COLLECTION_NAME_MAX_LEN) { + _jbr_ws_send_rc(wctx, key, JBR_ERROR_WS_INVALID_MESSAGE, + "Collection name exceeds maximum length allowed: " + "EJDB_COLLECTION_NAME_MAX_LEN"); + return; + } + memcpy(cnamebuf, coll, pos); + cnamebuf[pos] = '\0'; + coll = cnamebuf; + + if (wsop == JBWS_REMOVE_COLL) { + _jbr_ws_remove_coll(wctx, key, coll); + return; + } + + for (pos = 0; pos < len && isspace(data[pos]); ++pos) ; + len -= pos; + data += pos; + if (len < 1) { + _jbr_ws_send_rc(wctx, key, JBR_ERROR_WS_INVALID_MESSAGE, JBR_WS_STR_PREMATURE_END); + return; + } + + switch (wsop) { + case JBWS_ADD: + data[len] = '\0'; + _jbr_ws_add_document(wctx, key, coll, data); + break; + case JBWS_QUERY: + case JBWS_EXPLAIN: + data[len] = '\0'; + _jbr_ws_query(wctx, key, coll, data, (wsop == JBWS_EXPLAIN)); + break; + default: { + char nbuf[JBNUMBUF_SIZE]; + for (pos = 0; pos < len && pos < JBNUMBUF_SIZE - 1 && isdigit(data[pos]); ++pos) { + nbuf[pos] = data[pos]; + } + nbuf[pos] = '\0'; + for ( ; pos < len && isspace(data[pos]); ++pos) ; + len -= pos; + data += pos; + + int64_t id = iwatoi(nbuf); + if (id < 1) { + _jbr_ws_send_rc(wctx, key, JBR_ERROR_WS_INVALID_MESSAGE, "Invalid document id specified"); + return; + } + switch (wsop) { + case JBWS_GET: + _jbr_ws_get_document(wctx, key, coll, id); + break; + case JBWS_SET: + data[len] = '\0'; + _jbr_ws_set_document(wctx, key, coll, id, data); + break; + case JBWS_DEL: + _jbr_ws_del_document(wctx, key, coll, id); + break; + case JBWS_PATCH: + data[len] = '\0'; + _jbr_ws_patch_document(wctx, key, coll, id, data); + break; + case JBWS_IDX: + data[len] = '\0'; + _jbr_ws_set_index(wctx, key, coll, id, data); + break; + case JBWS_NIDX: + data[len] = '\0'; + _jbr_ws_del_index(wctx, key, coll, id, data); + break; + default: + _jbr_ws_send_rc(wctx, key, JBR_ERROR_WS_INVALID_MESSAGE, 0); + return; + } + } + } + } else { + data[len] = '\0'; + _jbr_ws_query(wctx, key, 0, data, false); + } +} + +static void _jbr_on_http_upgrade(http_s *req, char *requested_protocol, size_t len) { + JBR jbr = req->udata; + assert(jbr); + const EJDB_HTTP *http = jbr->http; + fio_str_info_s path = fiobj_obj2cstr(req->path); + + if ( ((path.len != 1) || (path.data[0] != '/')) + || ((len != 9) || (requested_protocol[1] != 'e'))) { + http_send_error(req, 400); + return; + } + JBWCTX *wctx = calloc(1, sizeof(*wctx)); + if (!wctx) { + http_send_error(req, 500); + return; + } + wctx->db = jbr->db; + + if (http->access_token) { + FIOBJ h = fiobj_hash_get2(req->headers, k_header_x_access_token_hash); + if (!h) { + if (http->read_anon) { + wctx->read_anon = true; + } else { + free(wctx); + http_send_error(req, 401); + return; + } + } + if (!fiobj_type_is(h, FIOBJ_T_STRING)) { // header specified more than once + free(wctx); + http_send_error(req, 400); + return; + } + fio_str_info_s hv = fiobj_obj2cstr(h); + if ((hv.len != http->access_token_len) || (memcmp(hv.data, http->access_token, http->access_token_len) != 0)) { // -V526 + free(wctx); + http_send_error(req, 403); + return; + } + } + if (http_upgrade2ws(req, + .on_message = _jbr_ws_on_message, + .on_open = _jbr_ws_on_open, + .on_close = _jbr_ws_on_close, + .udata = wctx) < 0) { + free(wctx); + JBR_RC_REPORT(500, req, JBR_ERROR_WS_UPGRADE); + } +} + +//---------------- Main --------------------- + +static void *_jbr_start_thread(void *op) { + JBR jbr = op; + char nbuf[JBNUMBUF_SIZE]; + const EJDB_HTTP *http = jbr->http; + const char *bind = http->bind ? http->bind : "localhost"; + if (http->port < 1) { + jbr->rc = JBR_ERROR_PORT_INVALID; + if (!jbr->http->blocking) { + pthread_barrier_wait(&jbr->start_barrier); + } + return 0; + } + iwitoa(http->port, nbuf, sizeof(nbuf)); + iwlog_info("HTTP/WS endpoint at %s:%s", bind, nbuf); + websocket_optimize4broadcasts(WEBSOCKET_OPTIMIZE_PUBSUB_TEXT, 1); + if (http_listen(nbuf, bind, + .udata = jbr, + .on_request = _jbr_on_http_request, + .on_upgrade = _jbr_on_http_upgrade, + .on_finish = _jbr_on_http_finish, + .max_body_size = http->max_body_size, + .ws_max_msg_size = http->max_body_size) == -1) { + jbr->rc = iwrc_set_errno(JBR_ERROR_HTTP_LISTEN, errno); + } + if (jbr->rc) { + if (!jbr->http->blocking) { + pthread_barrier_wait(&jbr->start_barrier); + } + return 0; + } + fio_state_callback_add(FIO_CALL_PRE_START, _jbr_on_pre_start, jbr); + fio_start(.threads = -2, .workers = 1, .is_no_signal_handlers = !jbr->http->blocking); // Will block current thread + // here + return 0; +} + +static void _jbr_release(JBR *pjbr) { + JBR jbr = *pjbr; + free(jbr); + *pjbr = 0; +} + +iwrc jbr_start(EJDB db, const EJDB_OPTS *opts, JBR *pjbr) { + iwrc rc; + *pjbr = 0; + if (!opts->http.enabled) { + return 0; + } + JBR jbr = calloc(1, sizeof(*jbr)); + if (!jbr) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + jbr->db = db; + jbr->terminated = true; + jbr->http = &opts->http; + + if (!jbr->http->blocking) { + int rci = pthread_barrier_init(&jbr->start_barrier, 0, 2); + if (rci) { + free(jbr); + return iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci); + } + rci = pthread_create(&jbr->worker_thread, 0, _jbr_start_thread, jbr); + if (rci) { + pthread_barrier_destroy(&jbr->start_barrier); + free(jbr); + return iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci); + } + pthread_barrier_wait(&jbr->start_barrier); + pthread_barrier_destroy(&jbr->start_barrier); + jbr->terminated = false; + rc = jbr->rc; + if (rc) { + jbr_shutdown(pjbr); + return rc; + } + *pjbr = jbr; + } else { + *pjbr = jbr; + jbr->terminated = false; + _jbr_start_thread(jbr); // Will block here + rc = jbr->rc; + jbr->terminated = true; + IWRC(jbr_shutdown(pjbr), rc); + } + return rc; +} + +iwrc jbr_shutdown(JBR *pjbr) { + if (!*pjbr) { + return 0; + } + JBR jbr = *pjbr; + if (__sync_bool_compare_and_swap(&jbr->terminated, 0, 1)) { + fio_state_callback_remove(FIO_CALL_PRE_START, _jbr_on_pre_start, jbr); + fio_stop(); + if (!jbr->http->blocking) { + pthread_join(jbr->worker_thread, 0); + } + } + _jbr_release(pjbr); + *pjbr = 0; + return 0; +} + +static const char *_jbr_ecodefn(locale_t locale, uint32_t ecode) { + if (!((ecode > _JBR_ERROR_START) && (ecode < _JBR_ERROR_END))) { + return 0; + } + switch (ecode) { + case JBR_ERROR_HTTP_LISTEN: + return "Failed to start HTTP network listener (JBR_ERROR_HTTP_LISTEN)"; + case JBR_ERROR_PORT_INVALID: + return "Invalid port specified (JBR_ERROR_PORT_INVALID)"; + case JBR_ERROR_SEND_RESPONSE: + return "Error sending response (JBR_ERROR_SEND_RESPONSE)"; + case JBR_ERROR_WS_UPGRADE: + return "Failed upgrading to websocket connection (JBR_ERROR_WS_UPGRADE)"; + case JBR_ERROR_WS_INVALID_MESSAGE: + return "Invalid message recieved (JBR_ERROR_WS_INVALID_MESSAGE)"; + case JBR_ERROR_WS_ACCESS_DENIED: + return "Access denied (JBR_ERROR_WS_ACCESS_DENIED)"; + } + return 0; +} + +iwrc jbr_init() { + static int _jbr_initialized = 0; + if (!__sync_bool_compare_and_swap(&_jbr_initialized, 0, 1)) { + return 0; + } + k_header_x_access_token_hash = fiobj_hash_string("x-access-token", 14); + k_header_x_hints_hash = fiobj_hash_string("x-hints", 7); + k_header_content_length_hash = fiobj_hash_string("content-length", 14); + k_header_content_type_hash = fiobj_hash_string("content-type", 12); + return iwlog_register_ecodefn(_jbr_ecodefn); +} diff --git a/src/jbr/jbr.h b/src/jbr/jbr.h new file mode 100644 index 0000000..b763550 --- /dev/null +++ b/src/jbr/jbr.h @@ -0,0 +1,56 @@ +#pragma once +#ifndef JBREST_H +#define JBREST_H + +/************************************************************************************************** + * EJDB2 + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +#include "ejdb2.h" + +IW_EXTERN_C_START + +typedef enum { + _JBR_ERROR_START = (IW_ERROR_START + 15000UL + 3000), + JBR_ERROR_HTTP_LISTEN, /**< Failed to start HTTP network listener (JBR_ERROR_HTTP_LISTEN) */ + JBR_ERROR_PORT_INVALID, /**< Invalid port specified (JBR_ERROR_PORT_INVALID) */ + JBR_ERROR_SEND_RESPONSE, /**< Error sending response (JBR_ERROR_SEND_RESPONSE) */ + JBR_ERROR_WS_UPGRADE, /**< Failed upgrading to websocket connection (JBR_ERROR_WS_UPGRADE) */ + JBR_ERROR_WS_INVALID_MESSAGE, /**< Invalid message recieved (JBR_ERROR_WS_INVALID_MESSAGE) */ + JBR_ERROR_WS_ACCESS_DENIED, /**< Access denied (JBR_ERROR_WS_ACCESS_DENIED) */ + _JBR_ERROR_END, +} jbr_ecode_t; + +struct _JBR; +typedef struct _JBR*JBR; + +iwrc jbr_start(EJDB db, const EJDB_OPTS *opts, JBR *pjbr); + +iwrc jbr_shutdown(JBR *pjbr); + +iwrc jbr_init(void); + +IW_EXTERN_C_END +#endif diff --git a/src/jbr/tests/CMakeLists.txt b/src/jbr/tests/CMakeLists.txt new file mode 100644 index 0000000..12a71cb --- /dev/null +++ b/src/jbr/tests/CMakeLists.txt @@ -0,0 +1,19 @@ +include(FindCURL) +if (NOT CURL_FOUND) + message(FATAL_ERROR "Cannot find libcurl library") +endif () + +link_libraries(ejdb2_s ${CUNIT_LIBRARIES} ${CURL_LIBRARIES}) +include_directories(${CUNIT_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/src/tests) + +set(TEST_DATA_DIR ${CMAKE_CURRENT_BINARY_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${TEST_DATA_DIR}) +set(TESTS jbr_test1) + +foreach (TN IN ITEMS ${TESTS}) + add_executable(${TN} ${TN}.c) + set_target_properties(${TN} PROPERTIES + COMPILE_FLAGS "-DIW_STATIC") + add_test(NAME ${TN} WORKING_DIRECTORY ${TEST_DATA_DIR} + COMMAND ${TEST_TOOL_CMD} $) +endforeach () diff --git a/src/jbr/tests/jbr_test1.c b/src/jbr/tests/jbr_test1.c new file mode 100644 index 0000000..9ba68b7 --- /dev/null +++ b/src/jbr/tests/jbr_test1.c @@ -0,0 +1,286 @@ +#include "ejdb_test.h" +#include +#include + +CURL *curl; + +int init_suite() { + curl_global_init(CURL_GLOBAL_ALL); + curl = curl_easy_init(); + if (!curl) { + return 1; + } + int rc = ejdb_init(); + return rc; +} + +int clean_suite() { + curl_easy_cleanup(curl); + curl_global_cleanup(); + return 0; +} + +static size_t curl_write_xstr(void *contents, size_t size, size_t nmemb, void *op) { + IWXSTR *xstr = op; + assert(xstr); + iwxstr_cat(xstr, contents, size * nmemb); + return size * nmemb; +} + +static void jbr_test1_1() { + char url[64]; + uint32_t port = iwu_rand_range(20000) + 20000; + + EJDB_OPTS opts = { + .kv = { + .path = "jbr_test1_1.db", + .oflags = IWKV_TRUNC + }, + .no_wal = true, + .http = { + .bind = "127.0.0.1", + .blocking = false, + .enabled = true, + .port = port + } + }; + + long code; + EJDB db; + iwrc rc = ejdb_open(&opts, &db); + if (rc) { + iwlog_ecode_error3(rc); + } + CU_ASSERT_EQUAL_FATAL(rc, 0); + + IWXSTR *xstr = iwxstr_new(); + IWXSTR *hstr = iwxstr_new(); + struct curl_slist *headers = curl_slist_append(0, "Content-Type: application/json"); + + snprintf(url, sizeof(url), "http://localhost:%" PRIu32 "/c1/1", port); + + // Check no element in collection + CURLcode cc = curl_easy_setopt(curl, CURLOPT_URL, url); + CU_ASSERT_EQUAL_FATAL(cc, 0); + cc = curl_easy_perform(curl); + CU_ASSERT_EQUAL_FATAL(cc, 0); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + CU_ASSERT_EQUAL_FATAL(code, 404); + + + snprintf(url, sizeof(url), "http://localhost:%" PRIu32 "/c1", port); + + // Save a document using POST + curl_easy_reset(curl); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); + CU_ASSERT_EQUAL_FATAL(cc, 0); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{\"foo\":\"bar\"}"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_xstr); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, xstr); + cc = curl_easy_perform(curl); + CU_ASSERT_EQUAL_FATAL(cc, 0); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + CU_ASSERT_EQUAL_FATAL(code, 200); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "1"); + + // Now get document JSON using GET + curl_easy_reset(curl); + iwxstr_clear(xstr); + iwxstr_clear(hstr); + + snprintf(url, sizeof(url), "http://localhost:%" PRIu32 "/c1/1", port); + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_xstr); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, xstr); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_write_xstr); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, hstr); + cc = curl_easy_perform(curl); + CU_ASSERT_EQUAL_FATAL(cc, 0); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + CU_ASSERT_EQUAL_FATAL(code, 200); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), + "{\n" + " \"foo\": \"bar\"\n" + "}" + ); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(hstr), "content-type:application/json")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(hstr), "content-length:17")); + CU_ASSERT_EQUAL(iwxstr_size(xstr), 17); + + // PUT document under specific ID + curl_easy_reset(curl); + iwxstr_clear(xstr); + iwxstr_clear(hstr); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); + CU_ASSERT_EQUAL_FATAL(cc, 0); + + snprintf(url, sizeof(url), "http://localhost:%" PRIu32 "/c1/33", port); + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{\"foo\":\"b\nar\"}"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_xstr); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, xstr); + cc = curl_easy_perform(curl); + CU_ASSERT_EQUAL_FATAL(cc, 0); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + CU_ASSERT_EQUAL_FATAL(code, 200); + + // Check the last doc + curl_easy_reset(curl); + iwxstr_clear(xstr); + iwxstr_clear(hstr); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_xstr); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, xstr); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_write_xstr); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, hstr); + cc = curl_easy_perform(curl); + CU_ASSERT_EQUAL_FATAL(cc, 0); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + CU_ASSERT_EQUAL_FATAL(code, 200); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), + "{\n" + " \"foo\": \"b\\nar\"\n" + "}" + ); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(hstr), "content-type:application/json")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(hstr), "content-length:19")); + CU_ASSERT_EQUAL(iwxstr_size(xstr), 19); + + // Perform a query + curl_easy_reset(curl); + iwxstr_clear(xstr); + iwxstr_clear(hstr); + + snprintf(url, sizeof(url), "http://localhost:%" PRIu32 "/", port); + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "@c1/foo"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_xstr); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, xstr); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_write_xstr); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, hstr); + cc = curl_easy_perform(curl); + CU_ASSERT_EQUAL_FATAL(cc, 0); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + CU_ASSERT_EQUAL_FATAL(code, 200); + + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(xstr), "33\t{\"foo\":\"b\\nar\"}")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(xstr), "1\t{\"foo\":\"bar\"}")); + + // Query with explain + curl_easy_reset(curl); + iwxstr_clear(xstr); + iwxstr_clear(hstr); + curl_slist_free_all(headers); + headers = curl_slist_append(0, "X-Hints: explain"); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "@c1/foo"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_xstr); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, xstr); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_write_xstr); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, hstr); + cc = curl_easy_perform(curl); + CU_ASSERT_EQUAL_FATAL(cc, 0); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + CU_ASSERT_EQUAL_FATAL(code, 200); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(xstr), "[INDEX] NO [COLLECTOR] PLAIN")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(xstr), "--------------------")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(xstr), "33\t{\"foo\":\"b\\nar\"}")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(xstr), "1\t{\"foo\":\"bar\"}")); + + // Delete resource + curl_easy_reset(curl); + iwxstr_clear(xstr); + iwxstr_clear(hstr); + + snprintf(url, sizeof(url), "http://localhost:%" PRIu32 "/c1/33", port); + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + cc = curl_easy_perform(curl); + CU_ASSERT_EQUAL_FATAL(cc, 0); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + CU_ASSERT_EQUAL_FATAL(code, 200); + + // Check the resource has been deleted + curl_easy_reset(curl); + iwxstr_clear(xstr); + iwxstr_clear(hstr); + curl_easy_setopt(curl, CURLOPT_URL, url); + cc = curl_easy_perform(curl); + CU_ASSERT_EQUAL_FATAL(cc, 0); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + CU_ASSERT_EQUAL_FATAL(code, 404); + + // Apply patch + curl_easy_reset(curl); + iwxstr_clear(xstr); + iwxstr_clear(hstr); + curl_slist_free_all(headers); + headers = curl_slist_append(0, "Content-Type: application/json"); + + snprintf(url, sizeof(url), "http://localhost:%" PRIu32 "/c1/1", port); + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "[{\"op\":\"replace\", \"path\":\"/foo\", \"value\":\"zzz\"}]"); + cc = curl_easy_perform(curl); + CU_ASSERT_EQUAL_FATAL(cc, 0); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + CU_ASSERT_EQUAL_FATAL(code, 200); + + // Check patch applied + curl_easy_reset(curl); + iwxstr_clear(xstr); + iwxstr_clear(hstr); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_xstr); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, xstr); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_write_xstr); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, hstr); + cc = curl_easy_perform(curl); + CU_ASSERT_EQUAL_FATAL(cc, 0); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + CU_ASSERT_EQUAL_FATAL(code, 200); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), + "{\n" + " \"foo\": \"zzz\"\n" + "}" + ); + + + iwxstr_destroy(xstr); + iwxstr_destroy(hstr); + curl_slist_free_all(headers); + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); +} + +int main() { + CU_pSuite pSuite = NULL; + if (CUE_SUCCESS != CU_initialize_registry()) { + return CU_get_error(); + } + pSuite = CU_add_suite("jbr_test1", init_suite, clean_suite); + if (NULL == pSuite) { + CU_cleanup_registry(); + return CU_get_error(); + } + if ( + (NULL == CU_add_test(pSuite, "jbr_test1_1", jbr_test1_1))) { + CU_cleanup_registry(); + return CU_get_error(); + } + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + int ret = CU_get_error() || CU_get_number_of_failures(); + CU_cleanup_registry(); + return ret; +} diff --git a/src/jbs/CMakeLists.txt b/src/jbs/CMakeLists.txt new file mode 100644 index 0000000..3d32206 --- /dev/null +++ b/src/jbs/CMakeLists.txt @@ -0,0 +1,14 @@ +# Executables +add_executable(jbs jbs.c) +target_link_libraries(jbs ejdb2_s) + +if(CMAKE_BUILD_TYPE STREQUAL "Release") + add_custom_command( + TARGET jbs + POST_BUILD + COMMAND strip -s $) +endif() + +if(DO_INSTALL_CORE) + install(TARGETS jbs RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() diff --git a/src/jbs/README.md b/src/jbs/README.md new file mode 100644 index 0000000..f8a3af2 --- /dev/null +++ b/src/jbs/README.md @@ -0,0 +1,31 @@ +# Standalone REST/Websocket HTTP server + +## jbs server +``` +jbs -h + +EJDB 2.0.0 standalone REST/Websocket server. http://ejdb.org + + --file <> Database file path. Default: db.jb + -f <> (same as --file) + --port ## HTTP port number listen to. Default: 9191 + -p ## (same as --port) + --bind <> Address server listen. Default: localhost + -b <> (same as --bind) + --access <> Server access token matched to 'X-Access-Token' HTTP header value + -a <> (same as --access) + --trunc Cleanup existing database file on open + -t (same as --trunc) + --wal Use write ahead logging (WAL). Must be set for data durability. + -w (same as --wal) + +Advanced options + --sbz ## Max sorting buffer size. If exceeded, an overflow temp file for data will be created. Default: 16777216, min: 1048576 + --dsz ## Initial size of buffer to process/store document on queries. Preferable average size of document. Default: 65536, min: 16384 + --bsz ## Max HTTP/WS API document body size. Default: 67108864, min: 524288 + +Use any of the following input formats: + -arg -arg= -arg + +Use the -h, -help or -? to get this information again. +``` diff --git a/src/jbs/jbs.c b/src/jbs/jbs.c new file mode 100644 index 0000000..6c3fe4d --- /dev/null +++ b/src/jbs/jbs.c @@ -0,0 +1,105 @@ +#include "ejdb2.h" +#include "ejdb2cfg.h" + +#include +#include +#include +#include + +EJDB db; // -V707 +EJDB_OPTS opts; + +int main(int argc, char const *argv[]) { + iwrc rc = 0; + fio_cli_start(argc, argv, 0, 0, + "EJDB " EJDB2_VERSION " standalone HTTP REST/Websocket server. http://ejdb.org\n", + FIO_CLI_STRING("--file -f Database file path. Default: ejdb2.db"), + FIO_CLI_INT("--port -p HTTP port number listen to. Default: 9191"), + FIO_CLI_STRING("--bind -b Address server listen. Default: localhost"), + FIO_CLI_STRING("--access -a Server access token matched to 'X-Access-Token' HTTP header value.\n" + "If token prefixed by '@' it will be treated as file."), + FIO_CLI_BOOL("--trunc -t Cleanup existing database file on open"), + FIO_CLI_BOOL("--wal -w Use write ahead logging (WAL). Must be set for data durability."), + FIO_CLI_BOOL("--cors Enable Cross-Origin Resource Sharing (CORS)."), + FIO_CLI_PRINT_HEADER("Advanced options"), + FIO_CLI_INT( + "--sbz Max sorting buffer size. If exceeded, an overflow temp file for data will be created. " + "Default: 16777216, min: 1048576"), + FIO_CLI_INT("--dsz Initial size of buffer to process/store document on queries. " + "Preferable average size of document. " + "Default: 65536, min: 16384"), + FIO_CLI_INT("--bsz Max HTTP/WS API document body size. " "Default: 67108864, min: 524288"), + FIO_CLI_BOOL("--trylock Fail if database is locked by another process." + " If not set, current process will wait for lock release") + + ); + fio_cli_set_default("--file", "ejdb2.db"); + fio_cli_set_default("-f", "ejdb2.db"); + fio_cli_set_default("--port", "9191"); + fio_cli_set_default("-p", "9191"); + fio_cli_set_default("--sbz", "16777216"); + fio_cli_set_default("--dsz", "65536"); + fio_cli_set_default("--bsz", "67108864"); + + char access_token_buf[255]; + const char *access_token = fio_cli_get("-a"); + if (access_token && (*access_token == '@')) { + access_token = access_token + 1; + FILE *f = fopen(access_token, "r"); + if (!f) { + rc = iwrc_set_errno(IW_ERROR_IO_ERRNO, errno); + goto finish; + } + size_t n = fread(access_token_buf, 1, sizeof(access_token_buf), f); + if ((n == 0) || (n == sizeof(access_token_buf))) { + fclose(f); + rc = IW_ERROR_INVALID_VALUE; + iwlog_error("Invalid access token from %s", access_token); + goto finish; + } + access_token_buf[n] = '\0'; + for (int i = 0; i < n; ++i) { + if (isspace(access_token_buf[i])) { + access_token_buf[i] = '\0'; + break; + } + } + fclose(f); + access_token = access_token_buf; + } else if (access_token && (strlen(access_token) >= sizeof(access_token_buf))) { + rc = IW_ERROR_INVALID_VALUE; + iwlog_error2("Invalid access token"); + goto finish; + } + + EJDB_OPTS ov = { + .kv = { + .path = fio_cli_get("-f"), + .oflags = fio_cli_get_i("-t") ? IWKV_TRUNC : 0, + .file_lock_fail_fast = fio_cli_get_bool("--trylock") + }, + .no_wal = !fio_cli_get_i("-w"), + .sort_buffer_sz = fio_cli_get_i("--sbz"), + .document_buffer_sz = fio_cli_get_i("--dsz"), + .http = { + .enabled = true, + .blocking = true, + .port = fio_cli_get_i("-p"), + .bind = fio_cli_get("-b"), + .access_token = access_token, + .max_body_size = fio_cli_get_i("--bsz"), + .cors = fio_cli_get_bool("--cors") + } + }; + memcpy(&opts, &ov, sizeof(ov)); + + rc = ejdb_open(&opts, &db); + RCGO(rc, finish); + IWRC(ejdb_close(&db), rc); +finish: + fio_cli_end(); + if (rc) { + iwlog_ecode_error3(rc); + } + return rc ? 1 : 0; +} diff --git a/src/jql/README.md b/src/jql/README.md new file mode 100644 index 0000000..6d23c86 --- /dev/null +++ b/src/jql/README.md @@ -0,0 +1,727 @@ +# JQL + +EJDB query language (JQL) syntax inspired by ideas behind XPath and Unix shell pipes. +It designed for easy querying and updating sets of JSON documents. + +## JQL grammar + +JQL parser created created by +[peg/leg — recursive-descent parser generators for C](http://piumarta.com/software/peg/) Here is the formal parser grammar: https://github.com/Softmotions/ejdb/blob/master/src/jql/jqp.leg + +## Non formal JQL grammar adapted for brief overview + +Notation used below is based on SQL syntax description: + +Rule | Description +--- | --- +`' '` | String in single quotes denotes unquoted string literal as part of query. +{ a | b } | Curly brackets enclose two or more required alternative choices, separated by vertical bars. +[ ] | Square brackets indicate an optional element or clause. Multiple elements or clauses are separated by vertical bars. +| | Vertical bars separate two or more alternative syntax elements. +... | Ellipses indicate that the preceding element can be repeated. The repetition is unlimited unless otherwise indicated. +( ) | Parentheses are grouping symbols. +Unquoted word in lower case| Denotes semantic of some query part. For example: `placeholder_name` - name of any placeholder. +``` +QUERY = FILTERS [ '|' APPLY ] [ '|' PROJECTIONS ] [ '|' OPTS ]; + +STR = { quoted_string | unquoted_string }; + +JSONVAL = json_value; + +PLACEHOLDER = { ':'placeholder_name | '?' } + +FILTERS = FILTER [{ and | or } [ not ] FILTER]; + + FILTER = [@collection_name]/NODE[/NODE]...; + + NODE = { '*' | '**' | NODE_EXPRESSION | STR }; + + NODE_EXPRESSION = '[' NODE_EXPR_LEFT OP NODE_EXPR_RIGHT ']' + [{ and | or } [ not ] NODE_EXPRESSION]...; + + OP = [ '!' ] { '=' | '>=' | '<=' | '>' | '<' | ~ } + | [ '!' ] { 'eq' | 'gte' | 'lte' | 'gt' | 'lt' } + | [ not ] { 'in' | 'ni' | 're' }; + + NODE_EXPR_LEFT = { '*' | '**' | STR | NODE_KEY_EXPR }; + + NODE_KEY_EXPR = '[' '*' OP NODE_EXPR_RIGHT ']' + + NODE_EXPR_RIGHT = JSONVAL | STR | PLACEHOLDER + +APPLY = { 'apply' | 'upsert' } { PLACEHOLDER | json_object | json_array } | 'del' + +OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }... + + ORDERBY = { 'asc' | 'desc' } PLACEHOLDER | json_path + +PROJECTIONS = PROJECTION [ {'+' | '-'} PROJECTION ] + + PROJECTION = 'all' | json_path + +``` + +* `json_value`: Any valid JSON value: object, array, string, bool, number. +* `json_path`: Simplified JSON pointer. Eg.: `/foo/bar` or `/foo/"bar with spaces"/` +* `*` in context of `NODE`: Any JSON object key name at particular nesting level. +* `**` in context of `NODE`: Any JSON object key name at arbitrary nesting level. +* `*` in context of `NODE_EXPR_LEFT`: Key name at specific level. +* `**` in context of `NODE_EXPR_LEFT`: Nested array value of array element under specific key. + +## JQL quick introduction + +Lets play with some very basic data and queries. +For simplicity we will use ejdb websocket network API which provides us a kind of interactive CLI. The same job can be done using pure `C` API too (`ejdb2.h jql.h`). + +NOTE: Take a look into [JQL test cases](https://github.com/Softmotions/ejdb/blob/master/src/jql/tests/jql_test1.c) for more examples. + +```json +{ + "firstName": "John", + "lastName": "Doe", + "age": 28, + "pets": [ + {"name": "Rexy rex", "kind": "dog", "likes": ["bones", "jumping", "toys"]}, + {"name": "Grenny", "kind": "parrot", "likes": ["green color", "night", "toys"]} + ] +} +``` +Save json as `sample.json` then upload it the `family` collection: + +```sh +# Start HTTP/WS server protected by some access token +./jbs -a 'myaccess01' +8 Mar 16:15:58.601 INFO: HTTP/WS endpoint at localhost:9191 +``` + +Server can be accessed using HTTP or Websocket endpoint. [More info](https://github.com/Softmotions/ejdb/blob/master/src/jbr/README.md) + +```sh +curl -d '@sample.json' -H'X-Access-Token:myaccess01' -X POST http://localhost:9191/family +``` + +We can play around using interactive [wscat](https://www.npmjs.com/package/@softmotions/wscat) websocket client. + +```sh +wscat -H 'X-Access-Token:myaccess01' -c http://localhost:9191 +connected (press CTRL+C to quit) +> k info +< k { + "version": "2.0.0", + "file": "db.jb", + "size": 8192, + "collections": [ + { + "name": "family", + "dbid": 3, + "rnum": 1, + "indexes": [] + } + ] +} + +> k get family 1 +< k 1 { + "firstName": "John", + "lastName": "Doe", + "age": 28, + "pets": [ + { + "name": "Rexy rex", + "kind": "dog", + "likes": [ + "bones", + "jumping", + "toys" + ] + }, + { + "name": "Grenny", + "kind": "parrot", + "likes": [ + "green color", + "night", + "toys" + ] + } + ] +} +``` + +Note about the `k` prefix before every command; It is an arbitrary key chosen by client and designated to identify particular +websocket request, this key will be returned with response to request and allows client to +identify that response for his particular request. [More info](https://github.com/Softmotions/ejdb/blob/master/src/jbr/README.md) + +Query command over websocket has the following format: + +``` + query +``` + +So we will consider only `` part in this document. + +### Get all elements in collection +``` +k query family /* +``` +or +``` +k query family /** +``` +or specify collection name in query explicitly +``` +k @family/* +``` + +We can execute query by HTTP `POST` request +``` +curl --data-raw '@family/[firstName = John]' -H'X-Access-Token:myaccess01' -X POST http://localhost:9191 + +1 {"firstName":"John","lastName":"Doe","age":28,"pets":[{"name":"Rexy rex","kind":"dog","likes":["bones","jumping","toys"]},{"name":"Grenny","kind":"parrot","likes":["green color","night","toys"]}]} +``` + +### Set the maximum number of elements in result set + +``` +k @family/* | limit 10 +``` + +### Get documents where specified json path exists + +Element at index `1` exists in `likes` array within a `pets` sub-object +``` +> k query family /pets/*/likes/1 +< k 1 {"firstName":"John"... +``` + +Element at index `1` exists in `likes` array at any `likes` nesting level +``` +> k query family /**/likes/1 +< k 1 {"firstName":"John"... +``` + +**From this point and below I will omit websocket specific prefix `k query family` and +consider only JQL queries.** + + +### Get documents by primary key + +In order to get documents by primary key the following options are available: + +1. Use API call `ejdb_get()` + ```ts + const doc = await db.get('users', 112); + ``` + +1. Use the special query construction: `/=:?` or `@collection/=:?` + +Get document from `users` collection with primary key `112` +``` +> k @users/=112 +``` + +Update tags array for document in `jobs` collection (TypeScript): +```ts + await db.createQuery('@jobs/ = :? | apply :? | count') + .setNumber(0, id) + .setJSON(1, { tags }) + .completionPromise(); +``` + +Array of primary keys can also be used for matching: + +```ts + await db.createQuery('@jobs/ = :?| apply :? | count') + .setJSON(0, [23, 1, 2]) + .setJSON(1, { tags }) + .completionPromise(); +``` + +### Matching JSON entry values + +Below is a set of self explaining queries: + +``` +/pets/*/[name = "Rexy rex"] + +/pets/*/[name eq "Rexy rex"] + +/pets/*/[name = "Rexy rex" or name = Grenny] +``` +Note about quotes around words with spaces. + +Get all documents where owner `age` greater than `20` and have some pet who like `bones` or `toys` +``` +/[age > 20] and /pets/*/likes/[** in ["bones", "toys"]] +``` +Here `**` denotes some element in `likes` array. + +`ni` is the inverse operator to `in`. +Get documents where `bones` somewhere in `likes` array. +``` +/pets/*/[likes ni "bones"] +``` + +We can create more complicated filters +``` +( /[age <= 20] or /[lastName re "Do.*"] ) + and /pets/*/likes/[** in ["bones", "toys"]] +``` +Note about grouping parentheses and regular expression matching using `re` operator. + +`~` is a prefix matching operator (Since ejdb `v2.0.53`). +Prefix matching can benefit from using indexes. + +Get documents where `/lastName` starts with `"Do"`. +``` +/[lastName ~ Do] +``` + +### Arrays and maps can be matched as is + +Filter documents with `likes` array exactly matched to `["bones","jumping","toys"]` +``` +/**/[likes = ["bones","jumping","toys"]] +``` +Matching algorithms for arrays and maps are different: + +* Array elements are matched from start to end. In equal arrays + all values at the same index should be equal. +* Object maps matching consists of the following steps: + * Lexicographically sort object keys in both maps. + * Do matching keys and its values starting from the lowest key. + * If all corresponding keys and values in one map are fully matched to ones in other + and vice versa, maps considered to be equal. + For example: `{"f":"d","e":"j"}` and `{"e":"j","f":"d"}` are equal maps. + +### Conditions on key names + +Find JSON document having `firstName` key at root level. +``` +/[* = "firstName"] +``` +I this context `*` denotes a key name. + +You can use conditions on key name and key value at the same time: +``` +/[[* = "firstName"] = John] +``` + +Key name can be either `firstName` or `lastName` but should have `John` value in any case. +``` +/[[* in ["firstName", "lastName"]] = John] +``` + +It may be useful in queries with dynamic placeholders (C API): +``` +/[[* = :keyName] = :keyValue] +``` + +## JQL data modification + +`APPLY` section responsible for modification of documents content. + +``` +APPLY = ({'apply' | `upsert`} { PLACEHOLDER | json_object | json_array }) | 'del' +``` + +JSON patch specs conformed to `rfc7386` or `rfc6902` specifications followed after `apply` keyword. + +Let's add `address` object to all matched document +``` +/[firstName = John] | apply {"address":{"city":"New York", "street":""}} +``` + +If JSON object is an argument of `apply` section it will be treated as merge match (`rfc7386`) otherwise +it should be array which denotes `rfc6902` JSON patch. Placeholders also supported by `apply` section. +``` +/* | apply :? +``` + +Set the street name in `address` +``` +/[firstName = John] | apply [{"op":"replace", "path":"/address/street", "value":"Fifth Avenue"}] +``` + +Add `Neo` fish to the set of John's `pets` +``` +/[firstName = John] +| apply [{"op":"add", "path":"/pets/-", "value": {"name":"Neo", "kind":"fish"}}] +``` + +`upsert` updates existing document by given json argument used as merge patch + or inserts provided json argument as new document instance. + +``` +/[firstName = John] | upsert {"firstName": "John", "address":{"city":"New York"}} +``` + +### Non standard JSON patch extensions + +#### increment + +Increments numeric value identified by JSON path by specified value. + +Example: +``` + Document: {"foo": 1} + Patch: [{"op": "increment", "path": "/foo", "value": 2}] + Result: {"foo": 3} +``` +#### add_create + +Same as JSON patch `add` but creates intermediate object nodes for missing JSON path segments. + +Example: +``` +Document: {"foo": {"bar": 1}} +Patch: [{"op": "add_create", "path": "/foo/zaz/gaz", "value": 22}] +Result: {"foo":{"bar":1,"zaz":{"gaz":22}}} +``` + +Example: +``` +Document: {"foo": {"bar": 1}} +Patch: [{"op": "add_create", "path": "/foo/bar/gaz", "value": 22}] +Result: Error since element pointed by /foo/bar is not an object +``` + +#### swap + +Swaps two values of JSON document starting from `from` path. + +Swapping rules + +1. If value pointed by `from` not exists error will be raised. +1. If value pointed by `path` not exists it will be set by value from `from` path, + then object pointed by `from` path will be removed. +1. If both values pointed by `from` and `path` are presented they will be swapped. + +Example: + +``` +Document: {"foo": ["bar"], "baz": {"gaz": 11}} +Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/gaz"}] +Result: {"foo": [11], "baz": {"gaz": "bar"}} +``` + +Example (Demo of rule 2): + +``` +Document: {"foo": ["bar"], "baz": {"gaz": 11}} +Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/zaz"}] +Result: {"foo":[],"baz":{"gaz":11,"zaz":"bar"}} +``` + +### Removing documents + +Use `del` keyword to remove matched elements from collection: +``` +/FILTERS | del +``` + +Example: +``` +> k add family {"firstName":"Jack"} +< k 2 +> k query family /[firstName re "Ja.*"] +< k 2 {"firstName":"Jack"} + +# Remove selected elements from collection +> k query family /[firstName=Jack] | del +< k 2 {"firstName":"Jack"} +``` + +## JQL projections + +``` +PROJECTIONS = PROJECTION [ {'+' | '-'} PROJECTION ] + + PROJECTION = 'all' | json_path | join_clause +``` + +Projection allows to get only subset of JSON document excluding not needed data. + +Lets add one more document to our collection: + +```sh +$ cat << EOF | curl -d @- -H'X-Access-Token:myaccess01' -X POST http://localhost:9191/family +{ +"firstName":"Jack", +"lastName":"Parker", +"age":35, +"pets":[{"name":"Sonic", "kind":"mouse", "likes":[]}] +} +EOF +``` +Now query only pet owners firstName and lastName from collection. + +``` +> k query family /* | /{firstName,lastName} + +< k 3 {"firstName":"Jack","lastName":"Parker"} +< k 1 {"firstName":"John","lastName":"Doe"} +< k +``` + +Add `pets` array for every document +``` +> k query family /* | /{firstName,lastName} + /pets + +< k 3 {"firstName":"Jack","lastName":"Parker","pets":[... +< k 1 {"firstName":"John","lastName":"Doe","pets":[... +``` + +Exclude only `pets` field from documents +``` +> k query family /* | all - /pets + +< k 3 {"firstName":"Jack","lastName":"Parker","age":35} +< k 1 {"firstName":"John","lastName":"Doe","age":28,"address":{"city":"New York","street":"Fifth Avenue"}} +< k +``` +Here `all` keyword used denoting whole document. + +Get `age` and the first pet in `pets` array. +``` +> k query family /[age > 20] | /age + /pets/0 + +< k 3 {"age":35,"pets":[{"name":"Sonic","kind":"mouse","likes":[]}]} +< k 1 {"age":28,"pets":[{"name":"Rexy rex","kind":"dog","likes":["bones","jumping","toys"]}]} +< k +``` + +## JQL collection joins + +Join materializes reference to document to a real document objects which will replace reference inplace. + +Documents are joined by their primary keys only. + +Reference keys should be stored in referrer document as number or string field. + +Joins can be specified as part of projection expression +in the following form: + +``` +/.../field k add artists {"name":"Leonardo Da Vinci", "years":[1452,1519]} +< k 1 +> k add paintings {"name":"Mona Lisa", "year":1490, "origin":"Italy", "artist": 1} +< k 1 +> k add paintings {"name":"Madonna Litta - Madonna And The Child", "year":1490, "origin":"Italy", "artist": 1} +< k 2 + +# Lists paintings documents + +> k @paintings/* +< k 2 {"name":"Madonna Litta - Madonna And The Child","year":1490,"origin":"Italy","artist":1} +< k 1 {"name":"Mona Lisa","year":1490,"origin":"Italy","artist":1} +< k +> + +# Do simple join with artists collection + +> k @paintings/* | /artist k @paintings/* | /artist + +# Same results as above: + +> k @paintings/* | /{name, artist k add paintings {"name":"Mona Lisa2", "year":1490, "origin":"Italy", "artist": 9999} +< k 3 +> k @paintings/* | /artist k add family {"firstName":"John", "lastName":"Ryan", "age":39} +< k 4 +``` + +``` +> k query family /* | /{firstName,lastName,age} | asc /firstName desc /age +< k 3 {"firstName":"Jack","lastName":"Parker","age":35} +< k 4 {"firstName":"John","lastName":"Ryan","age":39} +< k 1 {"firstName":"John","lastName":"Doe","age":28} +< k +``` + +`asc, desc` instructions may use indexes defined for collection to avoid a separate documents sorting stage. + +## JQL Options + +``` +OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }... +``` + +* `skip n` Skip first `n` records before first element in result set +* `limit n` Set max number of documents in result set +* `count` Returns only `count` of matched documents + ``` + > k query family /* | count + < k 3 + < k + ``` +* `noidx` Do not use any indexes for query execution. +* `inverse` By default query scans documents from most recently added to older ones. + This option inverts scan direction to opposite and activates `noidx` mode. + Has no effect if query has `asc/desc` sorting clauses. + +## JQL Indexes and performance tips + +Database index can be build for any JSON field path containing values of number or string type. +Index can be an `unique` ‐ not allowing value duplication and `non unique`. +The following index mode bit mask flags are used (defined in `ejdb2.h`): + +Index mode | Description +--- | --- +0x01 EJDB_IDX_UNIQUE | Index is unique +0x04 EJDB_IDX_STR | Index for JSON `string` field value type +0x08 EJDB_IDX_I64 | Index for `8 bytes width` signed integer field values +0x10 EJDB_IDX_F64 | Index for `8 bytes width` signed floating point field values. + +For example unique index of string type will be specified by `EJDB_IDX_UNIQUE | EJDB_IDX_STR` = `0x05`. +Index can be defined for only one value type located under specific path in json document. + +Lets define non unique string index for `/lastName` path: +``` +> k idx family 4 /lastName +< k +``` +Index selection for queries based on set of heuristic rules. + +You can always check index usage by issuing `explain` command in WS API: +``` +> k explain family /[lastName=Doe] and /[age!=27] +< k explain [INDEX] MATCHED STR|3 /lastName EXPR1: 'lastName = Doe' INIT: IWKV_CURSOR_EQ +[INDEX] SELECTED STR|3 /lastName EXPR1: 'lastName = Doe' INIT: IWKV_CURSOR_EQ + [COLLECTOR] PLAIN +``` + +The following statements are taken into account when using EJDB2 indexes: +* Only one index can be used for particular query execution +* If query consist of `or` joined part at top level or contains `negated` expressions at the top level + of query expression - indexes will not be in use at all. + So no indexes below: + ``` + /[lastName != Andy] + + /[lastName = "John"] or /[lastName = Peter] + + ``` + But will be used `/lastName` index defined above + ``` + /[lastName = Doe] + + /[lastName = Doe] and /[age = 28] + + /[lastName = Doe] and not /[age = 28] + + /[lastName = Doe] and /[age != 28] + ``` +* The following operators are supported by indexes (ejdb 2.0.x): + * `eq, =` + * `gt, >` + * `gte, >=` + * `lt, <` + * `lte, <=` + * `in` + * `~` (Prefix matching since ejdb 2.0.53) + +* `ORDERBY` clauses may use indexes to avoid result set sorting. +* Array fields can also be indexed. Let's outline typical use case: indexing of some entity tags: + ``` + > k add books {"name":"Mastering Ultra", "tags":["ultra", "language", "bestseller"]} + < k 1 + > k add books {"name":"Learn something in 24 hours", "tags":["bestseller"]} + < k 2 + > k query books /* + < k 2 {"name":"Learn something in 24 hours","tags":["bestseller"]} + < k 1 {"name":"Mastering Ultra","tags":["ultra","language","bestseller"]} + < k + ``` + Create string index for `/tags` + ``` + > k idx books 4 /tags + < k + ``` + Filter books by `bestseller` tag and show index usage in query: + ``` + > k explain books /tags/[** in ["bestseller"]] + < k explain [INDEX] MATCHED STR|4 /tags EXPR1: '** in ["bestseller"]' INIT: IWKV_CURSOR_EQ + [INDEX] SELECTED STR|4 /tags EXPR1: '** in ["bestseller"]' INIT: IWKV_CURSOR_EQ + [COLLECTOR] PLAIN + + < k 1 {"name":"Mastering Ultra","tags":["ultra","language","bestseller"]} + < k 2 {"name":"Learn something in 24 hours","tags":["bestseller"]} + < k + ``` + +### Performance tip: Physical ordering of documents + +All documents in collection are sorted by their primary key in `descending` order. +So if you use auto generated keys (`ejdb_put_new`) you may be sure what documents fetched as result of +full scan query will be ordered according to the time of insertion in descendant order, +unless you don't use query sorting, indexes or `inverse` keyword. + +### Performance tip: Brute force scan vs indexed access + +In many cases, using index may drop down the overall query performance. +Because index collection contains only document references (`id`) and engine may perform +an addition document fetching by its primary key to finish query matching. +So for not so large collections a brute scan may perform better than scan using indexes. +However, exact matching operations: `eq`, `in` and `sorting` by natural index order +will benefit from index in most cases. + + +### Performance tip: Get rid of unnecessary document data + +If you'd like update some set of documents with `apply` or `del` operations +but don't want fetching all of them as result of query - just add `count` +modifier to the query to get rid of unnecessary data transferring and json data conversion. diff --git a/src/jql/inc/jqpx.c b/src/jql/inc/jqpx.c new file mode 100644 index 0000000..0a66fb9 --- /dev/null +++ b/src/jql/inc/jqpx.c @@ -0,0 +1,1426 @@ +#include "jqp.h" +#include "utf8proc.h" +#include "jbl_internal.h" + +#include +#include + +#define MAX_ORDER_BY_CLAUSES 64 + +#define JQRC(yy_, rc_) do { \ + iwrc __rc = (rc_); \ + if (__rc) _jqp_fatal(yy_, __rc); \ +} while (0) + +static void _jqp_debug(yycontext *yy, const char *text) { + fprintf(stderr, "TEXT=%s\n", text); +} + +static void _jqp_fatal(yycontext *yy, iwrc rc) { + JQP_AUX *aux = yy->aux; + aux->rc = rc; + longjmp(aux->fatal_jmp, 1); +} + +static void *_jqp_malloc(struct _yycontext *yy, size_t size) { + void *ret = malloc(size); + if (!ret) { + JQP_AUX *aux = yy->aux; + aux->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + longjmp(aux->fatal_jmp, 1); + } + return ret; +} + +static void *_jqp_realloc(struct _yycontext *yy, void *ptr, size_t size) { + void *ret = realloc(ptr, size); + if (!ret) { + JQP_AUX *aux = yy->aux; + aux->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + longjmp(aux->fatal_jmp, 1); + } + return ret; +} + +static iwrc _jqp_aux_set_input(JQP_AUX *aux, const char *input) { + size_t len = strlen(input) + 1; + char *buf = iwpool_alloc(len, aux->pool); + if (!buf) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + memcpy(buf, input, len); + aux->buf = buf; + return 0; +} + +//----------------- + +IW_INLINE char *_jqp_strdup(struct _yycontext *yy, const char *text) { + iwrc rc = 0; + char *ret = iwpool_strdup(yy->aux->pool, text, &rc); + JQRC(yy, rc); + return ret; +} + +static JQPUNIT *_jqp_unit(yycontext *yy) { + JQPUNIT *ret = iwpool_calloc(sizeof(JQPUNIT), yy->aux->pool); + if (!ret) { + JQRC(yy, iwrc_set_errno(IW_ERROR_ALLOC, errno)); + } + return ret; +} + +static JQP_STACK *_jqp_push(yycontext *yy) { + JQP_AUX *aux = yy->aux; + JQP_STACK *stack; + if (aux->stackn < (sizeof(aux->stackpool) / sizeof(aux->stackpool[0]))) { + stack = &aux->stackpool[aux->stackn++]; + } else { + stack = malloc(sizeof(*aux->stack)); + if (!stack) { + JQRC(yy, iwrc_set_errno(IW_ERROR_ALLOC, errno)); + } + aux->stackn++; + } + memset(stack, 0, sizeof(*stack)); // -V575 + stack->next = 0; + if (!aux->stack) { + stack->prev = 0; + } else { + aux->stack->next = stack; + stack->prev = aux->stack; + } + aux->stack = stack; + return aux->stack; +} + +static JQP_STACK _jqp_pop(yycontext *yy) { + JQP_AUX *aux = yy->aux; + JQP_STACK *stack = aux->stack, ret; + if (!stack || (aux->stackn < 1)) { + iwlog_error2("Unbalanced stack"); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + aux->stack = stack->prev; + if (aux->stack) { + aux->stack->next = 0; + } + stack->prev = 0; + stack->next = 0; + ret = *stack; + if (aux->stackn-- > (sizeof(aux->stackpool) / sizeof(aux->stackpool[0]))) { + free(stack); + } + return ret; +} + +static void _jqp_unit_push(yycontext *yy, JQPUNIT *unit) { + JQP_STACK *stack = _jqp_push(yy); + stack->type = STACK_UNIT; + stack->unit = unit; +} + +static JQPUNIT *_jqp_unit_pop(yycontext *yy) { + JQP_STACK stack = _jqp_pop(yy); + if (stack.type != STACK_UNIT) { + iwlog_error("Unexpected type: %d", stack.type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + return stack.unit; +} + +static void _jqp_string_push(yycontext *yy, char *str, bool dup) { + JQP_STACK *stack = _jqp_push(yy); + stack->type = STACK_STRING; + stack->str = str; + if (dup) { + iwrc rc = 0; + JQP_AUX *aux = yy->aux; + stack->str = iwpool_strdup(aux->pool, stack->str, &rc); + if (rc) { + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + } +} + +static char *_jqp_string_pop(yycontext *yy) { + JQP_STACK stack = _jqp_pop(yy); + if (stack.type != STACK_STRING) { + iwlog_error("Unexpected type: %d", stack.type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + return stack.str; +} + +static JQPUNIT *_jqp_string(yycontext *yy, jqp_string_flavours_t flavour, const char *text) { + JQPUNIT *unit = _jqp_unit(yy); + unit->type = JQP_STRING_TYPE; + unit->string.flavour |= flavour; + unit->string.value = _jqp_strdup(yy, text); + return unit; +} + +static JQPUNIT *_jqp_number(yycontext *yy, jqp_int_flavours_t flavour, const char *text) { + JQPUNIT *unit = _jqp_unit(yy); + char *eptr; + int64_t ival = strtoll(text, &eptr, 0); + if ((eptr == text) || (errno == ERANGE)) { + iwlog_error("Invalid number: %s", text); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + if ((*eptr == '.') || (*eptr == 'e') || (*eptr == 'E')) { + unit->type = JQP_DOUBLE_TYPE; + unit->dblval.value = strtod(text, &eptr); + if ((eptr == text) || (errno == ERANGE)) { + iwlog_error("Invalid double number: %s", text); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + unit->dblval.flavour |= flavour; + } else { + unit->type = JQP_INTEGER_TYPE; + unit->intval.value = ival; + unit->intval.flavour |= flavour; + } + return unit; +} + +static JQPUNIT *_jqp_json_number(yycontext *yy, const char *text) { + JQPUNIT *unit = _jqp_unit(yy); + char *eptr; + unit->type = JQP_JSON_TYPE; + int64_t ival = strtoll(text, &eptr, 0); + if ((eptr == text) || (errno == ERANGE)) { + iwlog_error("Invalid number: %s", text); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + if ((*eptr == '.') || (*eptr == 'e') || (*eptr == 'E')) { + unit->json.jn.type = JBV_F64; + unit->json.jn.vf64 = strtod(text, &eptr); + if ((eptr == text) || (errno == ERANGE)) { + iwlog_error("Invalid double number: %s", text); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + } else { + unit->json.jn.type = JBV_I64; + unit->json.jn.vi64 = ival; + } + return unit; +} + +static JQPUNIT *_jqp_placeholder(yycontext *yy, const char *text) { + JQP_AUX *aux = yy->aux; + JQPUNIT *unit = _jqp_unit(yy); + unit->type = JQP_STRING_TYPE; + unit->string.flavour |= JQP_STR_PLACEHOLDER; + if (text[0] == '?') { + char nbuf[JBNUMBUF_SIZE + 1]; + nbuf[0] = '?'; + int len = iwitoa(aux->num_placeholders++, nbuf + 1, JBNUMBUF_SIZE); + nbuf[len + 1] = '\0'; + unit->string.value = _jqp_strdup(yy, nbuf); + } else { + unit->string.value = _jqp_strdup(yy, text); + } + if (!aux->start_placeholder) { + aux->start_placeholder = &unit->string; + aux->end_placeholder = aux->start_placeholder; + } else { + aux->end_placeholder->placeholder_next = &unit->string; + aux->end_placeholder = aux->end_placeholder->placeholder_next; + } + return unit; +} + +IW_INLINE int _jql_hex(char c) { + if ((c >= '0') && (c <= '9')) { + return c - '0'; + } + if ((c >= 'a') && (c <= 'f')) { + return c - 'a' + 10; + } + if ((c >= 'A') && (c <= 'F')) { + return c - 'A' + 10; + } + return -1; +} + +static int _jqp_unescape_json_string(const char *p, char *d, int dlen, iwrc *rcp) { + *rcp = 0; + char c; + char *ds = d; + char *de = d + dlen; + + while (1) { + c = *p++; + if (c == '\0') { + return d - ds; + } else if (c == '\\') { + switch (*p) { + case '\\': + case '/': + case '"': + if (d < de) { + *d = *p; + } + ++p, ++d; + break; + case 'b': + if (d < de) { + *d = '\b'; + } + ++p, ++d; + break; + case 'f': + if (d < de) { + *d = '\f'; + } + ++p, ++d; + break; + case 'n': + case 'r': + if (d < de) { + *d = '\n'; + } + ++p, ++d; + break; + case 't': + if (d < de) { + *d = '\t'; + } + ++p, ++d; + break; + case 'u': { + uint32_t cp, cp2; + int h1, h2, h3, h4; + if ( ((h1 = _jql_hex(p[1])) < 0) || ((h2 = _jql_hex(p[2])) < 0) + || ((h3 = _jql_hex(p[3])) < 0) || ((h4 = _jql_hex(p[4])) < 0)) { + *rcp = JBL_ERROR_PARSE_INVALID_CODEPOINT; + return 0; + } + cp = h1 << 12 | h2 << 8 | h3 << 4 | h4; + if ((cp & 0xfc00) == 0xd800) { + p += 6; + if ( (p[-1] != '\\') || (*p != 'u') + || ((h1 = _jql_hex(p[1])) < 0) || ((h2 = _jql_hex(p[2])) < 0) + || ((h3 = _jql_hex(p[3])) < 0) || ((h4 = _jql_hex(p[4])) < 0)) { + *rcp = JBL_ERROR_PARSE_INVALID_CODEPOINT; + return 0; + } + cp2 = h1 << 12 | h2 << 8 | h3 << 4 | h4; + if ((cp2 & 0xfc00) != 0xdc00) { + *rcp = JBL_ERROR_PARSE_INVALID_CODEPOINT; + return 0; + } + cp = 0x10000 + ((cp - 0xd800) << 10) + (cp2 - 0xdc00); + } + if (!utf8proc_codepoint_valid(cp)) { + *rcp = JBL_ERROR_PARSE_INVALID_CODEPOINT; + return 0; + } + uint8_t uchars[4]; + utf8proc_ssize_t ulen = utf8proc_encode_char(cp, uchars); + for (int i = 0; i < ulen; ++i) { + if (d < de) { + *d = uchars[i]; + } + ++d; + } + p += 5; + break; + } + default: + if (d < de) { + *d = c; + } + ++d; + } + } else { + if (d < de) { + *d = c; + } + ++d; + } + } + *rcp = JQL_ERROR_QUERY_PARSE; + return 0; +} + +static JQPUNIT *_jqp_unescaped_string(struct _yycontext *yy, jqp_string_flavours_t flv, const char *text) { + JQP_AUX *aux = yy->aux; + JQPUNIT *unit = _jqp_unit(yy); + unit->type = JQP_STRING_TYPE; + unit->string.flavour |= flv; + int len = _jqp_unescape_json_string(text, 0, 0, &aux->rc); + if (aux->rc) { + JQRC(yy, aux->rc); // -V547 + } + char *dest = iwpool_alloc(len + 1, aux->pool); + if (!dest) { + JQRC(yy, iwrc_set_errno(IW_ERROR_ALLOC, errno)); + } + _jqp_unescape_json_string(text, dest, len, &aux->rc); + if (aux->rc) { + JQRC(yy, aux->rc); // -V547 + } + dest[len] = '\0'; // -V1004 + unit->string.value = dest; + return unit; +} + +static JQPUNIT *_jqp_json_string(struct _yycontext *yy, const char *text) { + JQP_AUX *aux = yy->aux; + JQPUNIT *unit = _jqp_unit(yy); + unit->type = JQP_JSON_TYPE; + unit->json.jn.type = JBV_STR; + int len = _jqp_unescape_json_string(text, 0, 0, &aux->rc); + if (aux->rc) { + JQRC(yy, aux->rc); // -V547 + } + char *dest = iwpool_alloc(len + 1, aux->pool); + if (!dest) { + JQRC(yy, iwrc_set_errno(IW_ERROR_ALLOC, errno)); + } + _jqp_unescape_json_string(text, dest, len, &aux->rc); + if (aux->rc) { + JQRC(yy, aux->rc); // -V547 + } + dest[len] = '\0'; // -V1004 + unit->json.jn.vptr = dest; + unit->json.jn.vsize = len; + return unit; +} + +static JQPUNIT *_jqp_json_pair(yycontext *yy, JQPUNIT *key, JQPUNIT *val) { + if ((key->type != JQP_JSON_TYPE) || (val->type != JQP_JSON_TYPE) || (key->json.jn.type != JBV_STR)) { + iwlog_error2("Invalid arguments"); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + val->json.jn.key = key->json.jn.vptr; + val->json.jn.klidx = key->json.jn.vsize; + return val; +} + +static JQPUNIT *_jqp_json_collect(yycontext *yy, jbl_type_t type, JQPUNIT *until) { + JQP_AUX *aux = yy->aux; + JQPUNIT *ret = _jqp_unit(yy); + ret->type = JQP_JSON_TYPE; + JBL_NODE jn = &ret->json.jn; + jn->type = type; + while (aux->stack && aux->stack->type == STACK_UNIT) { + JQPUNIT *unit = aux->stack->unit; + if (unit == until) { + _jqp_pop(yy); + break; + } + if (unit->type != JQP_JSON_TYPE) { + iwlog_error("Unexpected type: %d", unit->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + JBL_NODE ju = &unit->json.jn; + if (!jn->child) { + jn->child = ju; + } else { + ju->next = jn->child; + ju->prev = jn->child->prev; + jn->child->prev = ju; + jn->child = ju; + } + _jqp_pop(yy); + } + return ret; +} + +static JQPUNIT *_jqp_json_true_false_null(yycontext *yy, const char *text) { + JQPUNIT *unit = _jqp_unit(yy); + unit->type = JQP_JSON_TYPE; + int len = strlen(text); + if (!strncmp("null", text, len)) { + unit->json.jn.type = JBV_NULL; + } else if (!strncmp("true", text, len)) { + unit->json.jn.type = JBV_BOOL; + unit->json.jn.vbool = true; + } else if (!strncmp("false", text, len)) { + unit->json.jn.type = JBV_BOOL; + unit->json.jn.vbool = false; + } else { + iwlog_error("Invalid json value: %s", text); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + return unit; +} + +static void _jqp_op_negate(yycontext *yy) { + yy->aux->negate = true; +} + +static void _jqp_op_negate_reset(yycontext *yy) { + yy->aux->negate = false; +} + +static JQPUNIT *_jqp_unit_op(yycontext *yy, const char *text) { + JQP_AUX *aux = yy->aux; + JQPUNIT *unit = _jqp_unit(yy); + unit->type = JQP_OP_TYPE; + unit->op.negate = aux->negate; + aux->negate = false; + if (!strcmp(text, "=") || !strcmp(text, "eq")) { + unit->op.value = JQP_OP_EQ; + } else if (!strcmp(text, ">") || !strcmp(text, "gt")) { + unit->op.value = JQP_OP_GT; + } else if (!strcmp(text, ">=") || !strcmp(text, "gte")) { + unit->op.value = JQP_OP_GTE; + } else if (!strcmp(text, "<") || !strcmp(text, "lt")) { + unit->op.value = JQP_OP_LT; + } else if (!strcmp(text, "<=") || !strcmp(text, "lte")) { + unit->op.value = JQP_OP_LTE; + } else if (!strcmp(text, "in")) { + unit->op.value = JQP_OP_IN; + } else if (!strcmp(text, "ni")) { + unit->op.value = JQP_OP_NI; + } else if (!strcmp(text, "re")) { + unit->op.value = JQP_OP_RE; + } else if (!(strcmp(text, "~"))) { + unit->op.value = JQP_OP_PREFIX; + } else { + iwlog_error("Invalid operation: %s", text); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + if (!aux->start_op) { + aux->start_op = &unit->op; + aux->end_op = aux->start_op; + } else { + aux->end_op->next = &unit->op; + aux->end_op = aux->end_op->next; + } + return unit; +} + +static JQPUNIT *_jqp_unit_join(yycontext *yy, const char *text) { + JQP_AUX *aux = yy->aux; + JQPUNIT *unit = _jqp_unit(yy); + unit->type = JQP_JOIN_TYPE; + unit->join.negate = aux->negate; + aux->negate = false; + if (!strcmp(text, "and")) { + unit->join.value = JQP_JOIN_AND; + } else if (!strcmp(text, "or")) { + unit->join.value = JQP_JOIN_OR; + } + return unit; +} + +static JQPUNIT *_jqp_expr(yycontext *yy, JQPUNIT *left, JQPUNIT *op, JQPUNIT *right) { + if (!left || !op || !right) { + iwlog_error2("Invalid arguments"); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + if ((op->type != JQP_OP_TYPE) && (op->type != JQP_JOIN_TYPE)) { + iwlog_error("Unexpected type: %d", op->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + JQPUNIT *unit = _jqp_unit(yy); + unit->type = JQP_EXPR_TYPE; + unit->expr.left = left; + unit->expr.op = &op->op; + unit->expr.right = right; + return unit; +} + +static JQPUNIT *_jqp_pop_expr_chain(yycontext *yy, JQPUNIT *until) { + JQPUNIT *expr = 0; + JQP_AUX *aux = yy->aux; + while (aux->stack && aux->stack->type == STACK_UNIT) { + JQPUNIT *unit = aux->stack->unit; + if (unit->type == JQP_EXPR_TYPE) { + if (expr) { + unit->expr.next = &expr->expr; + } + expr = unit; + } else if ((unit->type == JQP_JOIN_TYPE) && expr) { + expr->expr.join = &unit->join; + } else { + iwlog_error("Unexpected type: %d", unit->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + _jqp_pop(yy); + if (unit == until) { + break; + } + } + return expr; +} + +static JQPUNIT *_jqp_projection(struct _yycontext *yy, JQPUNIT *value, uint8_t flags) { + if (value->type != JQP_STRING_TYPE) { + iwlog_error("Unexpected type: %d", value->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + JQPUNIT *unit = _jqp_unit(yy); + unit->type = JQP_PROJECTION_TYPE; + unit->projection.value = &value->string; + unit->projection.flags |= flags; + return unit; +} + +static JQPUNIT *_jqp_pop_projection_nodes(yycontext *yy, JQPUNIT *until) { + JQPUNIT *first = 0; + JQP_AUX *aux = yy->aux; + uint8_t flags = 0; + + while (aux->stack && aux->stack->type == STACK_UNIT) { + JQPUNIT *unit = aux->stack->unit; + if (unit->type != JQP_STRING_TYPE) { + iwlog_error("Unexpected type: %d", unit->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + if (first) { + unit->string.next = &first->string; + } else if (unit->string.flavour & JQP_STR_PROJFIELD) { + for (JQP_STRING *s = &unit->string; s; s = s->subnext) { + if (s->flavour & JQP_STR_PROJOIN) { + flags |= JQP_PROJECTION_FLAG_JOINS; + } else { + flags |= JQP_PROJECTION_FLAG_INCLUDE; + } + } + } else if (strchr(unit->string.value, '<')) { // JOIN Projection? + unit->string.flavour |= JQP_STR_PROJOIN; + flags |= JQP_PROJECTION_FLAG_JOINS; + } + first = unit; + _jqp_pop(yy); + if (unit == until) { + break; + } + } + if (!flags) { + flags |= JQP_PROJECTION_FLAG_INCLUDE; + } + return _jqp_projection(yy, first, flags); +} + +static JQPUNIT *_jqp_push_joined_projection(struct _yycontext *yy, JQPUNIT *p) { + JQP_AUX *aux = yy->aux; + if (!aux->stack || (aux->stack->type != STACK_STRING)) { + iwlog_error2("Invalid stack state"); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + if (aux->stack->str[0] == '-') { + p->projection.flags &= ~JQP_PROJECTION_FLAG_INCLUDE; + p->projection.flags |= JQP_PROJECTION_FLAG_EXCLUDE; + } + _jqp_pop(yy); + _jqp_unit_push(yy, p); + return p; +} + +static JQPUNIT *_jqp_pop_joined_projections(yycontext *yy, JQPUNIT *until) { + JQPUNIT *first = 0; + JQP_AUX *aux = yy->aux; + while (aux->stack && aux->stack->type == STACK_UNIT) { + JQPUNIT *unit = aux->stack->unit; + if (unit->type != JQP_PROJECTION_TYPE) { + iwlog_error("Unexpected type: %d", unit->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + if (first) { + unit->projection.next = &first->projection; + } + first = unit; + _jqp_pop(yy); + if (unit == until) { + break; + } + } + return first; +} + +static JQPUNIT *_jqp_pop_projfields_chain(yycontext *yy, JQPUNIT *until) { + JQPUNIT *field = 0; + JQP_AUX *aux = yy->aux; + while (aux->stack && aux->stack->type == STACK_UNIT) { + JQPUNIT *unit = aux->stack->unit; + if (unit->type != JQP_STRING_TYPE) { + iwlog_error("Unexpected type: %d", unit->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + unit->string.flavour |= JQP_STR_PROJFIELD; + if (field) { + unit->string.subnext = &field->string; + } + if (strchr(unit->string.value, '<')) { // JOIN Projection? + unit->string.flavour |= JQP_STR_PROJOIN; + } + field = unit; + _jqp_pop(yy); + if (unit == until) { + break; + } + } + return field; +} + +static JQPUNIT *_jqp_pop_ordernodes(yycontext *yy, JQPUNIT *until) { + JQPUNIT *first = 0; + JQP_AUX *aux = yy->aux; + while (aux->stack && aux->stack->type == STACK_UNIT) { + JQPUNIT *unit = aux->stack->unit; + if (unit->type != JQP_STRING_TYPE) { + iwlog_error("Unexpected type: %d", unit->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + if (first) { + unit->string.subnext = &first->string; + } + first = unit; + _jqp_pop(yy); + if (unit == until) { + break; + } + } + return until; +} + +static JQPUNIT *_jqp_node(yycontext *yy, JQPUNIT *value) { + JQPUNIT *unit = _jqp_unit(yy); + unit->type = JQP_NODE_TYPE; + unit->node.value = value; + if (value->type == JQP_EXPR_TYPE) { + unit->node.ntype = JQP_NODE_EXPR; + } else if (value->type == JQP_STRING_TYPE) { + const char *str = value->string.value; + size_t len = strlen(str); + if (!strncmp("*", str, len)) { + unit->node.ntype = JQP_NODE_ANY; + } else if (!strncmp("**", str, len)) { + unit->node.ntype = JQP_NODE_ANYS; + } else { + unit->node.ntype = JQP_NODE_FIELD; + } + } else { + iwlog_error("Invalid node value type: %d", value->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + return unit; +} + +static JQPUNIT *_jqp_pop_node_chain(yycontext *yy, JQPUNIT *until) { + JQPUNIT *filter, *first = 0; + JQP_AUX *aux = yy->aux; + while (aux->stack && aux->stack->type == STACK_UNIT) { + JQPUNIT *unit = aux->stack->unit; + if (unit->type != JQP_NODE_TYPE) { + iwlog_error("Unexpected type: %d", unit->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + if (first) { + unit->node.next = &first->node; + } + first = unit; + _jqp_pop(yy); + if (unit == until) { + break; + } + } + if (!first) { + iwlog_error2("Invalid state"); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + filter = _jqp_unit(yy); + filter->type = JQP_FILTER_TYPE; + filter->filter.node = &first->node; + if ( aux->stack + && (aux->stack->type == STACK_UNIT) + && (aux->stack->unit->type == JQP_STRING_TYPE) + && (aux->stack->unit->string.flavour & JQP_STR_ANCHOR)) { + filter->filter.anchor = _jqp_unit_pop(yy)->string.value; + if (!aux->first_anchor) { + aux->first_anchor = filter->filter.anchor; + } + } + return filter; +} + +static JQPUNIT *_jqp_pop_filter_factor_chain(yycontext *yy, JQPUNIT *until) { + JQP_EXPR_NODE *factor = 0; + JQP_AUX *aux = yy->aux; + JQPUNIT *exprnode = _jqp_unit(yy); + while (aux->stack && aux->stack->type == STACK_UNIT) { + JQPUNIT *unit = aux->stack->unit; + if (unit->type == JQP_JOIN_TYPE) { + factor->join = &unit->join; // -V522 + } else if ((unit->type == JQP_EXPR_NODE_TYPE) || (unit->type == JQP_FILTER_TYPE)) { + JQP_EXPR_NODE *node = (JQP_EXPR_NODE*) unit; + if (factor) { + node->next = factor; + } + factor = node; + } else { + iwlog_error("Unexpected type: %d", unit->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + _jqp_pop(yy); + if (unit == until) { + break; + } + } + exprnode->type = JQP_EXPR_NODE_TYPE; + exprnode->exprnode.chain = factor; + return exprnode; +} + +static void _jqp_set_filters_expr(yycontext *yy, JQPUNIT *expr) { + JQP_AUX *aux = yy->aux; + if (expr->type != JQP_EXPR_NODE_TYPE) { + iwlog_error("Unexpected type: %d", expr->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + JQPUNIT *query = _jqp_unit(yy); + query->type = JQP_QUERY_TYPE; + query->query.aux = aux; + aux->expr = &expr->exprnode; + aux->query = &query->query; +} + +static JQPUNIT *_jqp_create_filterexpr_pk(yycontext *yy, JQPUNIT *argument) { + JQP_AUX *aux = yy->aux; + const char *anchor = 0; + // Looking for optional + if ( aux->stack + && (aux->stack->type == STACK_UNIT) + && (aux->stack->unit->type == JQP_STRING_TYPE) + && (aux->stack->unit->string.flavour & JQP_STR_ANCHOR)) { + anchor = _jqp_unit_pop(yy)->string.value; + if (!aux->first_anchor) { + aux->first_anchor = anchor; + } + } + JQPUNIT *unit = _jqp_unit(yy); + unit->type = JQP_EXPR_NODE_TYPE; + JQP_EXPR_NODE_PK *exprnode_pk = &unit->exprnode_pk; + exprnode_pk->flags = JQP_EXPR_NODE_FLAG_PK; + exprnode_pk->anchor = anchor; + exprnode_pk->argument = argument; + return unit; +} + +static void _jqp_set_apply(yycontext *yy, JQPUNIT *unit) { + JQP_AUX *aux = yy->aux; + if (!unit || !aux->query) { + iwlog_error2("Invalid arguments"); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + if (unit->type == JQP_JSON_TYPE) { + aux->apply = &unit->json.jn; + aux->apply_placeholder = 0; + } else if ((unit->type == JQP_STRING_TYPE) && (unit->string.flavour & JQP_STR_PLACEHOLDER)) { + aux->apply_placeholder = unit->string.value; + aux->apply = 0; + } else { + iwlog_error("Unexpected type: %d", unit->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } +} + +static void _jqp_set_apply_delete(yycontext *yy) { + JQP_AUX *aux = yy->aux; + aux->qmode |= JQP_QRY_APPLY_DEL; +} + +static void _jqp_set_apply_upsert(yycontext *yy, JQPUNIT *unit) { + JQP_AUX *aux = yy->aux; + aux->qmode |= JQP_QRY_APPLY_UPSERT; + _jqp_set_apply(yy, unit); +} + +static void _jqp_add_orderby(yycontext *yy, JQPUNIT *unit) { + JQP_AUX *aux = yy->aux; + if (unit->type != JQP_STRING_TYPE) { + iwlog_error("Unexpected type for order by: %d", unit->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + if (!aux->orderby) { + aux->orderby = &unit->string; + } else { + aux->orderby->next = &unit->string; + } +} + +static void _jqp_set_skip(yycontext *yy, JQPUNIT *unit) { + JQP_AUX *aux = yy->aux; + if ((unit->type != JQP_INTEGER_TYPE) && !( (unit->type == JQP_STRING_TYPE) + && (unit->string.flavour & JQP_STR_PLACEHOLDER))) { + iwlog_error("Unexpected type for skip: %d", unit->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + if (aux->skip) { + JQRC(yy, JQL_ERROR_SKIP_ALREADY_SET); + } + aux->skip = unit; +} + +static void _jqp_set_limit(yycontext *yy, JQPUNIT *unit) { + JQP_AUX *aux = yy->aux; + if ((unit->type != JQP_INTEGER_TYPE) && !( (unit->type == JQP_STRING_TYPE) + && (unit->string.flavour & JQP_STR_PLACEHOLDER))) { + iwlog_error("Unexpected type for limit: %d", unit->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + if (aux->limit) { + JQRC(yy, JQL_ERROR_LIMIT_ALREADY_SET); + } + aux->limit = unit; +} + +static void _jqp_set_aggregate_count(yycontext *yy) { + JQP_AUX *aux = yy->aux; + aux->qmode |= JQP_QRY_COUNT; + aux->projection = 0; // No projections in aggregate mode +} + +static void _jqp_set_noidx(yycontext *yy) { + JQP_AUX *aux = yy->aux; + aux->qmode |= JQP_QRY_NOIDX; +} + +static void _jqp_set_inverse(yycontext *yy) { + JQP_AUX *aux = yy->aux; + aux->qmode |= (JQP_QRY_NOIDX | JQP_QRY_INVERSE); +} + +static void _jqp_set_projection(yycontext *yy, JQPUNIT *unit) { + JQP_AUX *aux = yy->aux; + if (!unit || !aux->query) { + iwlog_error2("Invalid arguments"); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } + if (unit->type == JQP_PROJECTION_TYPE) { + JQP_PROJECTION *proj = &unit->projection; + for (JQP_PROJECTION *p = proj; p; p = p->next) { + if (p->value->flavour & JQP_STR_PROJALIAS) { + if (p->flags & JQP_PROJECTION_FLAG_EXCLUDE) { + aux->has_exclude_all_projection = true; + break; + } else { + proj = p->next; + } + } else if (!aux->has_keep_projections && (p->flags & JQP_PROJECTION_FLAG_INCLUDE)) { + aux->has_keep_projections = true; + } + } + aux->projection = proj; + } else { + iwlog_error("Unexpected type: %d", unit->type); + JQRC(yy, JQL_ERROR_QUERY_PARSE); + } +} + +static void _jqp_finish(yycontext *yy) { + iwrc rc = 0; + int cnt = 0; + IWXSTR *xstr = 0; + JQP_AUX *aux = yy->aux; + + JQP_STRING *orderby = aux->orderby; + for ( ; orderby; ++cnt, orderby = orderby->next) { + if (cnt >= MAX_ORDER_BY_CLAUSES) { + rc = JQL_ERROR_ORDERBY_MAX_LIMIT; + RCGO(rc, finish); + } + } + aux->orderby_num = cnt; + if (cnt) { + aux->orderby_ptrs = iwpool_alloc(cnt * sizeof(JBL_PTR), aux->pool); + if (!aux->orderby_ptrs) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + xstr = iwxstr_new(); + if (!xstr) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + RCGO(rc, finish); + } + cnt = 0; + orderby = aux->orderby; + for ( ; orderby; orderby = orderby->next) { + iwxstr_clear(xstr); + for (JQP_STRING *on = orderby; on; on = on->subnext) { + rc = iwxstr_cat(xstr, "/", 1); + RCGO(rc, finish); + iwxstr_cat(xstr, on->value, strlen(on->value)); + } + rc = jbl_ptr_alloc_pool(iwxstr_ptr(xstr), &aux->orderby_ptrs[cnt], aux->pool); + RCGO(rc, finish); + JBL_PTR ptr = aux->orderby_ptrs[cnt]; + ptr->op = (uint64_t) ((orderby->flavour & JQP_STR_NEGATE) != 0); // asc/desc + cnt++; + } + } + +finish: + if (xstr) { + iwxstr_destroy(xstr); + } + if (rc) { + aux->orderby_num = 0; + JQRC(yy, rc); + } +} + +iwrc jqp_aux_create(JQP_AUX **auxp, const char *input) { + iwrc rc = 0; + *auxp = calloc(1, sizeof(**auxp)); + if (!*auxp) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + JQP_AUX *aux = *auxp; + aux->xerr = iwxstr_new(); + if (!aux->xerr) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + aux->pool = iwpool_create(4 * 1024); + if (!aux->pool) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + rc = _jqp_aux_set_input(aux, input); + +finish: + if (rc) { + jqp_aux_destroy(auxp); + } + return rc; +} + +void jqp_aux_destroy(JQP_AUX **auxp) { + JQP_AUX *aux = *auxp; + if (aux) { + *auxp = 0; + if (aux->pool) { + iwpool_destroy(aux->pool); + } + if (aux->xerr) { + iwxstr_destroy(aux->xerr); + } + free(aux); + } +} + +IW_INLINE iwrc _iwxstr_cat2(IWXSTR *xstr, const char *buf) { + return iwxstr_cat(xstr, buf, strlen(buf)); +} + +static void yyerror(yycontext *yy) { + JQP_AUX *aux = yy->aux; + IWXSTR *xerr = aux->xerr; + if (yy->__pos && yy->__text[0]) { + _iwxstr_cat2(xerr, "near token: '"); + _iwxstr_cat2(xerr, yy->__text); + _iwxstr_cat2(xerr, "'\n"); + } + if (yy->__pos < yy->__limit) { + char buf[2] = { 0 }; + yy->__buf[yy->__limit] = '\0'; + _iwxstr_cat2(xerr, "\n"); + while (yy->__pos < yy->__limit) { + buf[0] = yy->__buf[yy->__pos++]; + iwxstr_cat(xerr, buf, 1); + } + } + _iwxstr_cat2(xerr, " <--- \n"); +} + +iwrc jqp_parse(JQP_AUX *aux) { + yycontext yy = { 0 }; + yy.aux = aux; + if (setjmp(aux->fatal_jmp)) { + if (aux->rc) { + iwlog_ecode_error3(aux->rc); + } + goto finish; + } + if (!yyparse(&yy)) { + if (!aux->rc) { + aux->rc = JQL_ERROR_QUERY_PARSE; + } + yyerror(&yy); + if (iwxstr_size(aux->xerr) && !(aux->mode & JQL_SILENT_ON_PARSE_ERROR)) { + const char *prefix = "Syntax error: "; + iwxstr_unshift(aux->xerr, prefix, strlen(prefix)); + iwlog_error("%s\n", iwxstr_ptr(aux->xerr)); + } + } + +finish: + yyrelease(&yy); + return aux->rc; +} + +#define PT(data_, size_, ch_, count_) do { \ + rc = pt(data_, size_, ch_, count_, op); \ + RCRET(rc); \ +} while (0) + +static iwrc _jqp_print_projection_nodes(const JQP_STRING *p, jbl_json_printer pt, void *op) { + iwrc rc = 0; + for (const JQP_STRING *s = p; s; s = s->next) { + if (!(s->flavour & JQP_STR_PROJALIAS)) { + PT(0, 0, '/', 1); + } + if (s->flavour & JQP_STR_PROJFIELD) { + PT(0, 0, '{', 1); + for (const JQP_STRING *pf = s; pf; pf = pf->subnext) { + PT(pf->value, -1, 0, 0); + if (pf->subnext) { + PT(0, 0, ',', 1); + } + } + PT(0, 0, '}', 1); + } else { + PT(s->value, -1, 0, 0); + } + } + return rc; +} + +static iwrc _jqp_print_projection(const JQP_PROJECTION *p, jbl_json_printer pt, void *op) { + iwrc rc = 0; + PT(0, 0, '|', 1); + for (int i = 0; p; p = p->next, ++i) { + PT(0, 0, ' ', 1); + if (i > 0) { + if (p->flags & JQP_PROJECTION_FLAG_EXCLUDE) { + PT("- ", 2, 0, 0); + } else { + PT("+ ", 2, 0, 0); + } + } else if (p->flags & JQP_PROJECTION_FLAG_EXCLUDE) { + PT("all - ", 6, 0, 0); + } + rc = _jqp_print_projection_nodes(p->value, pt, op); + RCRET(rc); + } + return rc; +} + +static iwrc _jqp_print_apply(const JQP_QUERY *q, jbl_json_printer pt, void *op) { + iwrc rc = 0; + if (q->aux->qmode & JQP_QRY_APPLY_DEL) { + PT("| del ", 6, 0, 0); + } else { + if (q->aux->qmode & JQP_QRY_APPLY_UPSERT) { + PT("| upsert ", 9, 0, 0); + } else { + PT("| apply ", 8, 0, 0); + } + if (q->aux->apply_placeholder) { + PT(q->aux->apply_placeholder, -1, 0, 0); + } else if (q->aux->apply) { + rc = jbn_as_json(q->aux->apply, pt, op, 0); + RCRET(rc); + } + } + return rc; +} + +static iwrc _jqp_print_join(jqp_op_t jqop, bool negate, jbl_json_printer pt, void *op) { + iwrc rc = 0; + PT(0, 0, ' ', 1); + if (jqop == JQP_OP_EQ) { + if (negate) { + PT(0, 0, '!', 1); + } + PT("= ", 2, 0, 0); + return rc; + } + if (jqop == JQP_JOIN_AND) { + PT("and ", 4, 0, 0); + if (negate) { + PT("not ", 4, 0, 0); + } + return rc; + } else if (jqop == JQP_JOIN_OR) { + PT("or ", 3, 0, 0); + if (negate) { + PT("not ", 4, 0, 0); + } + return rc; + } + if (negate) { + PT("not ", 4, 0, 0); + } + switch (jqop) { + case JQP_OP_GT: + PT(0, 0, '>', 1); + break; + case JQP_OP_LT: + PT(0, 0, '<', 1); + break; + case JQP_OP_GTE: + PT(">=", 2, 0, 0); + break; + case JQP_OP_LTE: + PT("<=", 2, 0, 0); + break; + case JQP_OP_IN: + PT("in", 2, 0, 0); + break; + case JQP_OP_RE: + PT("re", 2, 0, 0); + break; + case JQP_OP_PREFIX: + PT(0, 0, '~', 1); + break; + default: + iwlog_ecode_error3(IW_ERROR_ASSERTION); + rc = IW_ERROR_ASSERTION; + break; + } + PT(0, 0, ' ', 1); + return rc; +} + +IW_INLINE iwrc _print_placeholder(const char *value, jbl_json_printer pt, void *op) { + iwrc rc; + PT(0, 0, ':', 1); + if (value[0] == '?') { + PT(0, 0, '?', 1); + } else { + PT(value, -1, 0, 0); + } + return rc; +} + +iwrc jqp_print_filter_node_expr(const JQP_EXPR *e, jbl_json_printer pt, void *op) { + iwrc rc = 0; + if (e->left->type == JQP_EXPR_TYPE) { + PT(0, 0, '[', 1); + jqp_print_filter_node_expr(&e->left->expr, pt, op); + PT(0, 0, ']', 1); + } else if (e->left->type == JQP_STRING_TYPE) { + if (e->left->string.flavour & JQP_STR_QUOTED) { + PT(0, 0, '"', 1); + } + PT(e->left->string.value, -1, 0, 0); + if (e->left->string.flavour & JQP_STR_QUOTED) { + PT(0, 0, '"', 1); + } + } else { + iwlog_ecode_error3(IW_ERROR_ASSERTION); + return IW_ERROR_ASSERTION; + } + rc = _jqp_print_join(e->op->value, e->op->negate, pt, op); + RCRET(rc); + if (e->right->type == JQP_STRING_TYPE) { + if (e->right->string.flavour & JQP_STR_PLACEHOLDER) { + rc = _print_placeholder(e->right->string.value, pt, op); + RCRET(rc); + } else { + PT(e->right->string.value, -1, 0, 0); + } + } else if (e->right->type == JQP_JSON_TYPE) { + rc = jbn_as_json(&e->right->json.jn, pt, op, 0); + RCRET(rc); + } else { + iwlog_ecode_error3(IW_ERROR_ASSERTION); + return IW_ERROR_ASSERTION; + } + return rc; +} + +static iwrc _jqp_print_filter_node(const JQP_NODE *n, jbl_json_printer pt, void *op) { + iwrc rc = 0; + JQPUNIT *u = n->value; + PT(0, 0, '/', 1); + if (u->type == JQP_STRING_TYPE) { + PT(u->string.value, -1, 0, 0); + return rc; + } else if (u->type == JQP_EXPR_TYPE) { + PT(0, 0, '[', 1); + for (JQP_EXPR *e = &u->expr; e; e = e->next) { + if (e->join) { + rc = _jqp_print_join(e->join->value, e->join->negate, pt, op); + RCRET(rc); + } + rc = jqp_print_filter_node_expr(e, pt, op); + RCRET(rc); + } + PT(0, 0, ']', 1); + } else { + iwlog_ecode_error3(IW_ERROR_ASSERTION); + return IW_ERROR_ASSERTION; + } + return rc; +} + +static iwrc _jqp_print_filter( + const JQP_QUERY *q, + const JQP_FILTER *f, + jbl_json_printer pt, + void *op) { + iwrc rc = 0; + if (f->anchor) { + PT(0, 0, '@', 1); + PT(f->anchor, -1, 0, 0); + } + for (JQP_NODE *n = f->node; n; n = n->next) { + rc = _jqp_print_filter_node(n, pt, op); + RCRET(rc); + } + return rc; +} + +static iwrc _jqp_print_expression_node( + const JQP_QUERY *q, + const JQP_EXPR_NODE *en, + jbl_json_printer pt, + void *op) { + iwrc rc = 0; + bool inbraces = (en != q->aux->expr && en->type == JQP_EXPR_NODE_TYPE); + if (inbraces) { + PT(0, 0, '(', 1); + } + + // Primary key expression + if (en->flags & JQP_EXPR_NODE_FLAG_PK) { + JQP_EXPR_NODE_PK *pk = (void*) en; + if (pk->anchor) { + PT(0, 0, '@', 1); + PT(pk->anchor, -1, 0, 0); + } + PT("/=", 2, 0, 0); + if (!pk->argument) { + iwlog_ecode_error3(IW_ERROR_ASSERTION); + return IW_ERROR_ASSERTION; + } + if (pk->argument->type == JQP_STRING_TYPE) { + if (pk->argument->string.flavour & JQP_STR_PLACEHOLDER) { + rc = _print_placeholder(pk->argument->string.value, pt, op); + RCRET(rc); + } else { + PT(pk->argument->string.value, -1, 0, 0); + } + } else if (pk->argument->type == JQP_JSON_TYPE) { + rc = jbn_as_json(&pk->argument->json.jn, pt, op, 0); + RCRET(rc); + } else { + iwlog_ecode_error3(IW_ERROR_ASSERTION); + return IW_ERROR_ASSERTION; + } + return rc; + } + + for (en = en->chain; en; en = en->next) { + if (en->join) { + rc = _jqp_print_join(en->join->value, en->join->negate, pt, op); + RCRET(rc); + } + if (en->type == JQP_EXPR_NODE_TYPE) { + rc = _jqp_print_expression_node(q, en, pt, op); + RCRET(rc); + } else if (en->type == JQP_FILTER_TYPE) { + rc = _jqp_print_filter(q, (const JQP_FILTER*) en, pt, op); // -V1027 + } else { + iwlog_ecode_error3(IW_ERROR_ASSERTION); + return IW_ERROR_ASSERTION; + } + } + if (inbraces) { + PT(0, 0, ')', 1); + } + return rc; +} + +static iwrc _jqp_print_opts(const JQP_QUERY *q, jbl_json_printer pt, void *op) { + iwrc rc = 0; + int c = 0; + JQP_AUX *aux = q->aux; + PT(0, 0, '|', 1); + JQP_STRING *ob = aux->orderby; + while (ob) { + if (c++ > 0) { + PT("\n ", 2, 0, 0); + } + if (ob->flavour & JQP_STR_NEGATE) { + PT(" desc ", 6, 0, 0); + } else { + PT(" asc ", 5, 0, 0); + } + if (ob->flavour & JQP_STR_PLACEHOLDER) { + rc = _print_placeholder(ob->value, pt, op); + RCRET(rc); + } else { + // print orderby subnext chain + JQP_STRING *n = ob; + do { + PT(0, 0, '/', 1); + PT(n->value, -1, 0, 0); + } while ((n = n->subnext)); + } + ob = ob->next; + } + if (aux->skip || aux->limit) { + if (c > 0) { + PT("\n ", 2, 0, 0); + } + } + if (aux->skip) { + PT(" skip ", 6, 0, 0); + if (aux->skip->type == JQP_STRING_TYPE) { + if (aux->skip->string.flavour & JQP_STR_PLACEHOLDER) { + rc = _print_placeholder(aux->skip->string.value, pt, op); + RCRET(rc); + } else { + PT(aux->skip->string.value, -1, 0, 0); + } + } else if (aux->skip->type == JQP_INTEGER_TYPE) { + char nbuf[JBNUMBUF_SIZE]; + iwitoa(aux->skip->intval.value, nbuf, JBNUMBUF_SIZE); + PT(nbuf, -1, 0, 0); + } + } + if (aux->limit) { + PT(" limit ", 7, 0, 0); + if (aux->limit->type == JQP_STRING_TYPE) { + if (aux->limit->string.flavour & JQP_STR_PLACEHOLDER) { + rc = _print_placeholder(aux->limit->string.value, pt, op); + RCRET(rc); + } else { + PT(aux->limit->string.value, -1, 0, 0); + } + } else if (aux->limit->type == JQP_INTEGER_TYPE) { + char nbuf[JBNUMBUF_SIZE]; + iwitoa(aux->limit->intval.value, nbuf, JBNUMBUF_SIZE); + PT(nbuf, -1, 0, 0); + } + } + return rc; +} + +iwrc jqp_print_query(const JQP_QUERY *q, jbl_json_printer pt, void *op) { + if (!q || !pt) { + return IW_ERROR_INVALID_ARGS; + } + JQP_AUX *aux = q->aux; + iwrc rc = _jqp_print_expression_node(q, aux->expr, pt, op); + RCRET(rc); + if (aux->apply_placeholder || aux->apply) { + PT(0, 0, '\n', 1); + rc = _jqp_print_apply(q, pt, op); + RCRET(rc); + } + if (aux->projection) { + PT(0, 0, '\n', 1); + rc = _jqp_print_projection(aux->projection, pt, op); + RCRET(rc); + } + if (aux->skip || aux->limit || aux->orderby) { + PT(0, 0, '\n', 1); + rc = _jqp_print_opts(q, pt, op); + } + + return rc; +} + +#undef PT diff --git a/src/jql/jql.c b/src/jql/jql.c new file mode 100644 index 0000000..45145a8 --- /dev/null +++ b/src/jql/jql.c @@ -0,0 +1,1829 @@ +#include "convert.h" +#include "ejdb2_internal.h" +#include "jbl_internal.h" +#include "jql_internal.h" +#include "jqp.h" +#include "lwre.h" +#include + +/** Query matching context */ +typedef struct MCTX { + int lvl; + binn *bv; + const char *key; + struct _JQL *q; + JQP_AUX *aux; + JBL_VCTX *vctx; +} MCTX; + +/** Expression node matching context */ +typedef struct MENCTX { + bool matched; +} MENCTX; + +/** Filter matching context */ +typedef struct MFCTX { + bool matched; + int last_lvl; /**< Last matched level */ + JQP_NODE *nodes; + JQP_NODE *last_node; + JQP_FILTER *qpf; +} MFCTX; + +static JQP_NODE *_jql_match_node(MCTX *mctx, JQP_NODE *n, bool *res, iwrc *rcp); + +IW_INLINE void _jql_jqval_destroy(JQP_STRING *pv) { + JQVAL *qv = pv->opaque; + if (qv) { + void *ptr; + switch (qv->type) { + case JQVAL_STR: + ptr = (void*) qv->vstr; + break; + case JQVAL_RE: + ptr = (void*) qv->vre->expression; + lwre_free(qv->vre); + break; + case JQVAL_JBLNODE: + ptr = qv->vnode; + break; + default: + ptr = 0; + break; + } + if (ptr && qv->freefn) { + qv->freefn(ptr, qv->freefn_op); + } + pv->opaque = 0; + free(qv); + } +} + +static JQVAL *_jql_find_placeholder(JQL q, const char *name) { + JQP_AUX *aux = q->aux; + for (JQP_STRING *pv = aux->start_placeholder; pv; pv = pv->placeholder_next) { + if (!strcmp(pv->value, name)) { + return pv->opaque; + } + } + return 0; +} + +JQVAL *jql_find_placeholder(JQL q, const char *name) { + return _jql_find_placeholder(q, name); +} + +static iwrc _jql_set_placeholder(JQL q, const char *placeholder, int index, JQVAL *val) { + JQP_AUX *aux = q->aux; + if (!placeholder) { // Index + char nbuf[JBNUMBUF_SIZE]; + iwitoa(index, nbuf, JBNUMBUF_SIZE); + for (JQP_STRING *pv = aux->start_placeholder; pv; pv = pv->placeholder_next) { + if ((pv->value[0] == '?') && !strcmp(pv->value + 1, nbuf)) { + _jql_jqval_destroy(pv); + pv->opaque = val; + return 0; + } + } + } else { + for (JQP_STRING *pv = aux->start_placeholder; pv; pv = pv->placeholder_next) { + if (!strcmp(pv->value, placeholder)) { + _jql_jqval_destroy(pv); + pv->opaque = val; + return 0; + } + } + } + return JQL_ERROR_INVALID_PLACEHOLDER; +} + +iwrc jql_set_json2( + JQL q, const char *placeholder, int index, JBL_NODE val, + void (*freefn)(void*, void*), void *op) { + JQVAL *qv = malloc(sizeof(*qv)); + if (!qv) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + qv->freefn = freefn; + qv->freefn_op = op; + qv->type = JQVAL_JBLNODE; + qv->vnode = val; + return _jql_set_placeholder(q, placeholder, index, qv); +} + +iwrc jql_set_json(JQL q, const char *placeholder, int index, JBL_NODE val) { + return jql_set_json2(q, placeholder, index, val, 0, 0); +} + +static void _jql_free_iwpool(void *ptr, void *op) { + iwpool_destroy((IWPOOL*) op); +} + +iwrc jql_set_json_jbl(JQL q, const char *placeholder, int index, JBL jbl) { + IWPOOL *pool = iwpool_create(jbl_size(jbl)); + if (!pool) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + JBL_NODE n; + iwrc rc = jbl_to_node(jbl, &n, true, pool); + RCGO(rc, finish); + rc = jql_set_json2(q, placeholder, index, n, _jql_free_iwpool, pool); +finish: + if (rc) { + iwpool_destroy(pool); + } + return rc; +} + +iwrc jql_set_i64(JQL q, const char *placeholder, int index, int64_t val) { + JQVAL *qv = malloc(sizeof(*qv)); + if (!qv) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + qv->freefn = 0; + qv->freefn_op = 0; + qv->type = JQVAL_I64; + qv->vi64 = val; + return _jql_set_placeholder(q, placeholder, index, qv); +} + +iwrc jql_set_f64(JQL q, const char *placeholder, int index, double val) { + JQVAL *qv = malloc(sizeof(*qv)); + if (!qv) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + qv->freefn = 0; + qv->freefn_op = 0; + qv->type = JQVAL_F64; + qv->vf64 = val; + return _jql_set_placeholder(q, placeholder, index, qv); +} + +iwrc jql_set_str2( + JQL q, const char *placeholder, int index, const char *val, + void (*freefn)(void*, void*), void *op) { + JQVAL *qv = malloc(sizeof(*qv)); + if (!qv) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + qv->freefn = freefn; + qv->freefn_op = op; + qv->type = JQVAL_STR; + qv->vstr = val; + return _jql_set_placeholder(q, placeholder, index, qv); +} + +iwrc jql_set_str(JQL q, const char *placeholder, int index, const char *val) { + return jql_set_str2(q, placeholder, index, val, 0, 0); +} + +iwrc jql_set_bool(JQL q, const char *placeholder, int index, bool val) { + JQVAL *qv = malloc(sizeof(*qv)); + if (!qv) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + qv->freefn = 0; + qv->freefn_op = 0; + qv->type = JQVAL_BOOL; + qv->vbool = val; + return _jql_set_placeholder(q, placeholder, index, qv); +} + +iwrc jql_set_regexp2( + JQL q, const char *placeholder, int index, const char *expr, + void (*freefn)(void*, void*), void *op) { + struct re *rx = lwre_new(expr); + if (!rx) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + JQVAL *qv = malloc(sizeof(*qv)); + if (!qv) { + iwrc rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + lwre_free(rx); + return rc; + } + qv->freefn = freefn; + qv->freefn_op = op; + qv->type = JQVAL_RE; + qv->vre = rx; + return _jql_set_placeholder(q, placeholder, index, qv); +} + +iwrc jql_set_regexp(JQL q, const char *placeholder, int index, const char *expr) { + return jql_set_regexp2(q, placeholder, index, expr, 0, 0); +} + +iwrc jql_set_null(JQL q, const char *placeholder, int index) { + JQVAL *qv = malloc(sizeof(*qv)); + if (!qv) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + qv->freefn = 0; + qv->freefn_op = 0; + qv->type = JQVAL_NULL; + return _jql_set_placeholder(q, placeholder, index, qv); +} + +static bool _jql_need_deeper_match(JQP_EXPR_NODE *en, int lvl) { + for (en = en->chain; en; en = en->next) { + if (en->type == JQP_EXPR_NODE_TYPE) { + if (_jql_need_deeper_match(en, lvl)) { + return true; + } + } else if (en->type == JQP_FILTER_TYPE) { + MFCTX *fctx = ((JQP_FILTER*) en)->opaque; + if (!fctx->matched && (fctx->last_lvl == lvl)) { + return true; + } + } + } + return false; +} + +static void _jql_reset_expression_node(JQP_EXPR_NODE *en, JQP_AUX *aux, bool reset_match_cache) { + MENCTX *ectx = en->opaque; + ectx->matched = false; + for (en = en->chain; en; en = en->next) { + if (en->type == JQP_EXPR_NODE_TYPE) { + _jql_reset_expression_node(en, aux, reset_match_cache); + } else if (en->type == JQP_FILTER_TYPE) { + MFCTX *fctx = ((JQP_FILTER*) en)->opaque; + fctx->matched = false; + fctx->last_lvl = -1; + for (JQP_NODE *n = fctx->nodes; n; n = n->next) { + n->start = -1; + n->end = -1; + JQPUNIT *unit = n->value; + if (reset_match_cache && (unit->type == JQP_EXPR_TYPE)) { + for (JQP_EXPR *expr = &unit->expr; expr; expr = expr->next) expr->prematched = false; + } + } + } + } +} + +static iwrc _jql_init_expression_node(JQP_EXPR_NODE *en, JQP_AUX *aux) { + en->opaque = iwpool_calloc(sizeof(MENCTX), aux->pool); + if (!en->opaque) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + for (en = en->chain; en; en = en->next) { + if (en->type == JQP_EXPR_NODE_TYPE) { + iwrc rc = _jql_init_expression_node(en, aux); + RCRET(rc); + } else if (en->type == JQP_FILTER_TYPE) { + MFCTX *fctx = iwpool_calloc(sizeof(*fctx), aux->pool); + JQP_FILTER *f = (JQP_FILTER*) en; + if (!fctx) { + return iwrc_set_errno(IW_ERROR_ALLOC, errno); + } + f->opaque = fctx; + fctx->last_lvl = -1; + fctx->qpf = f; + fctx->nodes = f->node; + for (JQP_NODE *n = f->node; n; n = n->next) { + fctx->last_node = n; + n->start = -1; + n->end = -1; + } + } + } + return 0; +} + +iwrc jql_create2(JQL *qptr, const char *coll, const char *query, jql_create_mode_t mode) { + if (!qptr || !query) { + return IW_ERROR_INVALID_ARGS; + } + *qptr = 0; + + JQL q; + JQP_AUX *aux; + iwrc rc = jqp_aux_create(&aux, query); + RCRET(rc); + + q = iwpool_calloc(sizeof(*q), aux->pool); + if (!q) { + rc = iwrc_set_errno(IW_ERROR_ALLOC, errno); + goto finish; + } + aux->mode = mode; + q->aux = aux; + + rc = jqp_parse(aux); + RCGO(rc, finish); + + if (coll) { + // Get a copy of collection name + coll = iwpool_strdup(aux->pool, coll, &rc); + RCGO(rc, finish); + } + + q->coll = coll; + q->qp = aux->query; + + if (!q->coll) { + // Try to set collection from first query anchor + q->coll = aux->first_anchor; + if (!q->coll) { + rc = JQL_ERROR_NO_COLLECTION; + goto finish; + } + } + + rc = _jql_init_expression_node(aux->expr, aux); + +finish: + if (rc) { + if ( (rc == JQL_ERROR_QUERY_PARSE) + && (mode & JQL_KEEP_QUERY_ON_PARSE_ERROR)) { + *qptr = q; + } else { + jqp_aux_destroy(&aux); + } + } else { + *qptr = q; + } + return rc; +} + +iwrc jql_create(JQL *qptr, const char *coll, const char *query) { + return jql_create2(qptr, coll, query, 0); +} + +size_t jql_estimate_allocated_size(JQL q) { + size_t ret = sizeof(struct _JQL); + if (q->aux && q->aux->pool) { + ret += iwpool_allocated_size(q->aux->pool); + } + return ret; +} + +const char *jql_collection(JQL q) { + return q->coll; +} + +void jql_reset(JQL q, bool reset_match_cache, bool reset_placeholders) { + q->matched = false; + q->dirty = false; + JQP_AUX *aux = q->aux; + _jql_reset_expression_node(aux->expr, aux, reset_match_cache); + if (reset_placeholders) { + for (JQP_STRING *pv = aux->start_placeholder; pv; pv = pv->placeholder_next) { // Cleanup placeholders + _jql_jqval_destroy(pv); + } + } +} + +void jql_destroy(JQL *qptr) { + if (!qptr) { + return; + } + JQL q = *qptr; + if (q) { + JQP_AUX *aux = q->aux; + for (JQP_STRING *pv = aux->start_placeholder; pv; pv = pv->placeholder_next) { // Cleanup placeholders + _jql_jqval_destroy(pv); + } + for (JQP_OP *op = aux->start_op; op; op = op->next) { + if (op->opaque) { + if (op->value == JQP_OP_RE) { + lwre_free(op->opaque); + } + } + } + jqp_aux_destroy(&aux); + } + *qptr = 0; +} + +IW_INLINE jqval_type_t _jql_binn_to_jqval(binn *vbinn, JQVAL *qval) { + switch (vbinn->type) { + case BINN_OBJECT: + case BINN_MAP: + case BINN_LIST: + qval->type = JQVAL_BINN; + qval->vbinn = vbinn; + return qval->type; + case BINN_NULL: + qval->type = JQVAL_NULL; + return qval->type; + case BINN_STRING: + qval->type = JQVAL_STR; + qval->vstr = vbinn->ptr; + return qval->type; + case BINN_BOOL: + case BINN_TRUE: + case BINN_FALSE: + qval->type = JQVAL_BOOL; + qval->vbool = vbinn->vbool != 0; + return qval->type; + case BINN_UINT8: + qval->type = JQVAL_I64; + qval->vi64 = vbinn->vuint8; + return qval->type; + case BINN_UINT16: + qval->type = JQVAL_I64; + qval->vi64 = vbinn->vuint16; + return qval->type; + case BINN_UINT32: + qval->type = JQVAL_I64; + qval->vi64 = vbinn->vuint32; + return qval->type; + case BINN_UINT64: + qval->type = JQVAL_I64; + qval->vi64 = (int64_t) vbinn->vuint64; + return qval->type; + case BINN_INT8: + qval->type = JQVAL_I64; + qval->vi64 = vbinn->vint8; // NOLINT(bugprone-signed-char-misuse) + return qval->type; + case BINN_INT16: + qval->type = JQVAL_I64; + qval->vi64 = vbinn->vint16; + return qval->type; + case BINN_INT32: + qval->type = JQVAL_I64; + qval->vi64 = vbinn->vint32; + return qval->type; + case BINN_INT64: + qval->type = JQVAL_I64; + qval->vi64 = vbinn->vint64; + return qval->type; + case BINN_FLOAT32: + qval->type = JQVAL_F64; + qval->vf64 = vbinn->vfloat; + return qval->type; + case BINN_FLOAT64: + qval->type = JQVAL_F64; + qval->vf64 = vbinn->vdouble; + return qval->type; + default: + memset(qval, 0, sizeof(*qval)); + break; + } + return JQVAL_NULL; +} + +jqval_type_t jql_binn_to_jqval(binn *vbinn, JQVAL *qval) { + return _jql_binn_to_jqval(vbinn, qval); +} + +IW_INLINE void _jql_node_to_jqval(JBL_NODE jn, JQVAL *qv) { + switch (jn->type) { + case JBV_STR: + qv->type = JQVAL_STR; + qv->vstr = jn->vptr; + break; + case JBV_I64: + qv->type = JQVAL_I64; + qv->vi64 = jn->vi64; + break; + case JBV_BOOL: + qv->type = JQVAL_BOOL; + qv->vbool = jn->vbool; + break; + case JBV_F64: + qv->type = JQVAL_F64; + qv->vf64 = jn->vf64; + break; + case JBV_NULL: + case JBV_NONE: + qv->type = JQVAL_NULL; + break; + case JBV_OBJECT: + case JBV_ARRAY: + qv->type = JQVAL_JBLNODE; + qv->vnode = jn; + break; + default: + qv->type = JQVAL_NULL; + break; + } +} + +void jql_node_to_jqval(JBL_NODE jn, JQVAL *qv) { + _jql_node_to_jqval(jn, qv); +} + +/** + * Allowed on left: JQVAL_STR|JQVAL_I64|JQVAL_F64|JQVAL_BOOL|JQVAL_NULL|JQVAL_BINN + * Allowed on right: JQVAL_STR|JQVAL_I64|JQVAL_F64|JQVAL_BOOL|JQVAL_NULL|JQVAL_JBLNODE + */ +static int _jql_cmp_jqval_pair(const JQVAL *left, const JQVAL *right, iwrc *rcp) { + JQVAL sleft, sright; // Stack allocated left/right converted values + const JQVAL *lv = left, *rv = right; + + if (lv->type == JQVAL_BINN) { + _jql_binn_to_jqval(lv->vbinn, &sleft); + lv = &sleft; + } + if (rv->type == JQVAL_JBLNODE) { + _jql_node_to_jqval(rv->vnode, &sright); + rv = &sright; + } + + switch (lv->type) { + case JQVAL_STR: + switch (rv->type) { + case JQVAL_STR: { + int l1 = (int) strlen(lv->vstr); + int l2 = (int) strlen(rv->vstr); + if (l1 != l2) { + return l1 - l2; + } + return strncmp(lv->vstr, rv->vstr, l1); + } + case JQVAL_BOOL: + return !strcmp(lv->vstr, "true") - rv->vbool; + case JQVAL_I64: { + char nbuf[JBNUMBUF_SIZE]; + iwitoa(rv->vi64, nbuf, JBNUMBUF_SIZE); + return strcmp(lv->vstr, nbuf); + } + case JQVAL_F64: { + size_t osz; + char nbuf[JBNUMBUF_SIZE]; + jbi_ftoa(rv->vf64, nbuf, &osz); + return strcmp(lv->vstr, nbuf); + } + case JQVAL_NULL: + return (!lv->vstr || lv->vstr[0] == '\0') ? 0 : 1; + default: + break; + } + break; + case JQVAL_I64: + switch (rv->type) { + case JQVAL_I64: + return lv->vi64 > rv->vi64 ? 1 : lv->vi64 < rv->vi64 ? -1 : 0; + case JQVAL_F64: + return (double) lv->vi64 > rv->vf64 ? 1 : (double) lv->vi64 < rv->vf64 ? -1 : 0; + case JQVAL_STR: { + int64_t rval = iwatoi(rv->vstr); + return lv->vi64 > rval ? 1 : lv->vi64 < rval ? -1 : 0; + } + case JQVAL_NULL: + return 1; + case JQVAL_BOOL: { + return (lv->vi64 != 0) - rv->vbool; + } + default: + break; + } + break; + case JQVAL_F64: + switch (rv->type) { + case JQVAL_F64: + return lv->vf64 > rv->vf64 ? 1 : lv->vf64 < rv->vf64 ? -1 : 0; + case JQVAL_I64: + return lv->vf64 > (double) rv->vi64 ? 1 : lv->vf64 < rv->vf64 ? -1 : 0; + case JQVAL_STR: { + double rval = (double) iwatof(rv->vstr); + return lv->vf64 > rval ? 1 : lv->vf64 < rval ? -1 : 0; + } + case JQVAL_NULL: + return 1; + case JQVAL_BOOL: + return lv->vf64 > (double) rv->vbool ? 1 : lv->vf64 < (double) rv->vbool ? -1 : 0; + default: + break; + } + break; + case JQVAL_BOOL: + switch (rv->type) { + case JQVAL_BOOL: + return lv->vbool - rv->vbool; + case JQVAL_I64: + return lv->vbool - (rv->vi64 != 0L); + case JQVAL_F64: + return lv->vbool - (rv->vf64 != 0.0); // -V550 + case JQVAL_STR: + return lv->vbool - !strcmp(rv->vstr, "true"); + case JQVAL_NULL: + return lv->vbool; + default: + break; + } + break; + case JQVAL_NULL: + switch (rv->type) { + case JQVAL_NULL: + return 0; + case JQVAL_STR: + return (!rv->vstr || rv->vstr[0] == '\0') ? 0 : -1; + default: + return -1; + } + break; + case JQVAL_BINN: { + if ( (rv->type != JQVAL_JBLNODE) + || ((rv->vnode->type == JBV_ARRAY) && (lv->vbinn->type != BINN_LIST)) + || ((rv->vnode->type == JBV_OBJECT) && ((lv->vbinn->type != BINN_OBJECT) && (lv->vbinn->type != BINN_MAP)))) { + // Incompatible types + *rcp = _JQL_ERROR_UNMATCHED; + return 0; + } + JBL_NODE lnode; + IWPOOL *pool = iwpool_create(rv->vbinn->size * 2); + if (!pool) { + *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return 0; + } + *rcp = _jbl_node_from_binn(lv->vbinn, &lnode, false, pool); + if (*rcp) { + iwpool_destroy(pool); + return 0; + } + int cmp = jbn_compare_nodes(lnode, rv->vnode, rcp); + iwpool_destroy(pool); + return cmp; + } + default: + break; + } + *rcp = _JQL_ERROR_UNMATCHED; + return 0; +} + +int jql_cmp_jqval_pair(const JQVAL *left, const JQVAL *right, iwrc *rcp) { + return _jql_cmp_jqval_pair(left, right, rcp); +} + +static bool _jql_match_regexp( + JQP_AUX *aux, + JQVAL *left, JQP_OP *jqop, JQVAL *right, + iwrc *rcp) { + struct re *rx; + char nbuf[JBNUMBUF_SIZE]; + static_assert(JBNUMBUF_SIZE >= IWFTOA_BUFSIZE, "JBNUMBUF_SIZE >= IWFTOA_BUFSIZE"); + JQVAL sleft, sright; // Stack allocated left/right converted values + JQVAL *lv = left, *rv = right; + char *input = 0; + size_t rci, match_end = 0; + const char *expr = 0; + bool match_start = false; + + if (lv->type == JQVAL_JBLNODE) { + _jql_node_to_jqval(lv->vnode, &sleft); + lv = &sleft; + } else if (lv->type == JQVAL_BINN) { + _jql_binn_to_jqval(lv->vbinn, &sleft); + lv = &sleft; + } + if (lv->type >= JQVAL_JBLNODE) { + *rcp = _JQL_ERROR_UNMATCHED; + return false; + } + + if (jqop->opaque) { + rx = jqop->opaque; + } else if (right->type == JQVAL_RE) { + rx = right->vre; + } else { + if (rv->type == JQVAL_JBLNODE) { + _jql_node_to_jqval(rv->vnode, &sright); + rv = &sright; + } + switch (rv->type) { + case JQVAL_STR: + expr = rv->vstr; + break; + case JQVAL_I64: { + iwitoa(rv->vi64, nbuf, JBNUMBUF_SIZE); + expr = iwpool_strdup(aux->pool, nbuf, rcp); + if (*rcp) { + return false; + } + break; + } + case JQVAL_F64: { + size_t osz; + jbi_ftoa(rv->vf64, nbuf, &osz); + expr = iwpool_strdup(aux->pool, nbuf, rcp); + if (*rcp) { + return false; + } + break; + } + case JQVAL_BOOL: + expr = rv->vbool ? "true" : "false"; + break; + default: + *rcp = _JQL_ERROR_UNMATCHED; + return false; + } + + assert(expr); + if (expr[0] == '^') { + expr += 1; + match_start = true; + } + rci = strlen(expr); + if (rci && (expr[rci - 1] == '$')) { + char *aexpr = iwpool_alloc(rci, aux->pool); + if (!aexpr) { + *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return false; + } + match_end = rci - 1; + memcpy(aexpr, expr, match_end); + aexpr[rci - 1] = '\0'; + expr = aexpr; + } + rx = lwre_new(expr); + if (!rx) { + *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return false; + } + jqop->opaque = rx; + } + assert(rx); + + switch (lv->type) { + case JQVAL_STR: + input = (char*) lv->vstr; + break; + case JQVAL_I64: + iwitoa(lv->vi64, nbuf, JBNUMBUF_SIZE); + input = nbuf; + break; + case JQVAL_F64: { + size_t osz; + jbi_ftoa(lv->vf64, nbuf, &osz); + input = nbuf; + } + break; + case JQVAL_BOOL: + input = lv->vbool ? "true" : "false"; + break; + default: + *rcp = _JQL_ERROR_UNMATCHED; + return false; + } + + assert(input); + int mret = lwre_match(rx, input); + switch (mret) { + case RE_ERROR_NOMATCH: + return false; + case RE_ERROR_NOMEM: + *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return false; + case RE_ERROR_CHARSET: + *rcp = JQL_ERROR_REGEXP_CHARSET; + return false; + case RE_ERROR_SUBEXP: + *rcp = JQL_ERROR_REGEXP_SUBEXP; + return false; + case RE_ERROR_SUBMATCH: + *rcp = JQL_ERROR_REGEXP_SUBMATCH; + return false; + case RE_ERROR_ENGINE: + *rcp = JQL_ERROR_REGEXP_ENGINE; + iwlog_ecode_error3(JQL_ERROR_REGEXP_ENGINE); + return false; + } + if (mret > 0) { + if (match_start && (rx->position - mret != input)) { + return false; + } + if (match_end && (rx->position != input + match_end)) { + return false; + } + return true; + } + return false; +} + +static bool _jql_match_in( + JQVAL *left, JQP_OP *jqop, JQVAL *right, + iwrc *rcp) { + + JQVAL sleft; // Stack allocated left/right converted values + JQVAL *lv = left, *rv = right; + if ((rv->type != JQVAL_JBLNODE) && (rv->vnode->type != JBV_ARRAY)) { + *rcp = _JQL_ERROR_UNMATCHED; + return false; + } + if (lv->type == JQVAL_JBLNODE) { + _jql_node_to_jqval(lv->vnode, &sleft); + lv = &sleft; + } else if (lv->type == JQVAL_BINN) { + _jql_binn_to_jqval(lv->vbinn, &sleft); + lv = &sleft; + } + for (JBL_NODE n = rv->vnode->child; n; n = n->next) { + JQVAL qv = { + .type = JQVAL_JBLNODE, + .vnode = n + }; + if (!_jql_cmp_jqval_pair(lv, &qv, rcp)) { + if (*rcp) { + return false; + } + return true; + } + if (*rcp) { + return false; + } + } + return false; +} + +static bool _jql_match_ni( + JQVAL *left, JQP_OP *jqop, JQVAL *right, + iwrc *rcp) { + + JQVAL sleft; // Stack allocated left/right converted values + JQVAL *lv = left, *rv = right; + binn bv; + binn_iter iter; + if ((rv->type != JQVAL_BINN) || (rv->vbinn->type != BINN_LIST)) { + *rcp = _JQL_ERROR_UNMATCHED; + return false; + } + if (lv->type == JQVAL_JBLNODE) { + _jql_node_to_jqval(lv->vnode, &sleft); + lv = &sleft; + } else if (lv->type == JQVAL_BINN) { + _jql_binn_to_jqval(lv->vbinn, &sleft); + lv = &sleft; + } + if (lv->type >= JQVAL_JBLNODE) { + *rcp = _JQL_ERROR_UNMATCHED; + return false; + } + if (!binn_iter_init(&iter, rv->vbinn, rv->vbinn->type)) { + *rcp = JBL_ERROR_INVALID; + return false; + } + while (binn_list_next(&iter, &bv)) { + JQVAL qv = { + .type = JQVAL_BINN, + .vbinn = &bv + }; + if (!_jql_cmp_jqval_pair(&qv, lv, rcp)) { + if (*rcp) { + return false; + } + return true; + } else if (*rcp) { + return false; + } + } + return false; +} + +static bool _jql_match_starts( + JQVAL *left, JQP_OP *jqop, JQVAL *right, + iwrc *rcp) { + + JQVAL sleft; // Stack allocated left/right converted values + JQVAL *lv = left, *rv = right; + char nbuf[JBNUMBUF_SIZE]; + char nbuf2[JBNUMBUF_SIZE]; + char *input = 0, *prefix = 0; + + if (lv->type == JQVAL_JBLNODE) { + _jql_node_to_jqval(lv->vnode, &sleft); + lv = &sleft; + } else if (lv->type == JQVAL_BINN) { + _jql_binn_to_jqval(lv->vbinn, &sleft); + lv = &sleft; + } + switch (lv->type) { + case JQVAL_STR: + input = (char*) lv->vstr; + break; + case JQVAL_I64: + iwitoa(lv->vi64, nbuf, JBNUMBUF_SIZE); + input = nbuf; + break; + case JQVAL_F64: { + size_t osz; + jbi_ftoa(lv->vf64, nbuf, &osz); + input = nbuf; + break; + } + case JQVAL_BOOL: + input = lv->vbool ? "true" : "false"; + break; + default: + *rcp = _JQL_ERROR_UNMATCHED; + return false; + } + switch (rv->type) { + case JQVAL_STR: + prefix = (char*) rv->vstr; + break; + case JQVAL_I64: + iwitoa(rv->vi64, nbuf2, JBNUMBUF_SIZE); + prefix = nbuf2; + break; + case JQVAL_F64: { + size_t osz; + jbi_ftoa(rv->vf64, nbuf2, &osz); + prefix = nbuf2; + break; + } + case JQVAL_BOOL: + prefix = rv->vbool ? "true" : "false"; + break; + default: + *rcp = _JQL_ERROR_UNMATCHED; + return false; + } + size_t plen = strlen(prefix); + if (plen > 0) { + return strncmp(input, prefix, plen) == 0; + } else { + return true; + } +} + +static bool _jql_match_jqval_pair( + JQP_AUX *aux, + JQVAL *left, JQP_OP *jqop, JQVAL *right, + iwrc *rcp) { + bool match = false; + jqp_op_t op = jqop->value; + if ((op >= JQP_OP_EQ) && (op <= JQP_OP_LTE)) { + int cmp = _jql_cmp_jqval_pair(left, right, rcp); + if (*rcp) { + goto finish; + } + switch (op) { + case JQP_OP_EQ: + match = (cmp == 0); + break; + case JQP_OP_GT: + match = (cmp > 0); + break; + case JQP_OP_GTE: + match = (cmp >= 0); + break; + case JQP_OP_LT: + match = (cmp < 0); + break; + case JQP_OP_LTE: + match = (cmp <= 0); + break; + default: + break; + } + } else { + switch (op) { + case JQP_OP_RE: + match = _jql_match_regexp(aux, left, jqop, right, rcp); + break; + case JQP_OP_IN: + match = _jql_match_in(left, jqop, right, rcp); + break; + case JQP_OP_NI: + match = _jql_match_ni(right, jqop, left, rcp); + break; + case JQP_OP_PREFIX: + match = _jql_match_starts(left, jqop, right, rcp); + default: + break; + } + } + +finish: + if (*rcp) { + if (*rcp == _JQL_ERROR_UNMATCHED) { + *rcp = 0; + } + match = false; + } + if (jqop->negate) { + match = !match; + } + return match; +} + +bool jql_match_jqval_pair( + JQP_AUX *aux, + JQVAL *left, JQP_OP *jqop, JQVAL *right, + iwrc *rcp) { + return _jql_match_jqval_pair(aux, left, jqop, right, rcp); +} + +static JQVAL *_jql_unit_to_jqval(JQP_AUX *aux, JQPUNIT *unit, iwrc *rcp) { + *rcp = 0; + switch (unit->type) { + case JQP_STRING_TYPE: { + if (unit->string.opaque) { + return (JQVAL*) unit->string.opaque; + } + if (unit->string.flavour & JQP_STR_PLACEHOLDER) { + *rcp = JQL_ERROR_INVALID_PLACEHOLDER; + return 0; + } else { + JQVAL *qv = iwpool_calloc(sizeof(*qv), aux->pool); + if (!qv) { + *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return 0; + } + unit->string.opaque = qv; + qv->type = JQVAL_STR; + qv->vstr = unit->string.value; + } + return unit->string.opaque; + } + case JQP_JSON_TYPE: { + if (unit->json.opaque) { + return (JQVAL*) unit->json.opaque; + } + JQVAL *qv = iwpool_calloc(sizeof(*qv), aux->pool); + if (!qv) { + *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return 0; + } + unit->json.opaque = qv; + struct _JBL_NODE *jn = &unit->json.jn; + switch (jn->type) { + case JBV_BOOL: + qv->type = JQVAL_BOOL; + qv->vbool = jn->vbool; + break; + case JBV_I64: + qv->type = JQVAL_I64; + qv->vi64 = jn->vi64; + break; + case JBV_F64: + qv->type = JQVAL_F64; + qv->vf64 = jn->vf64; + break; + case JBV_STR: + qv->type = JQVAL_STR; + qv->vstr = jn->vptr; + break; + case JBV_NULL: + qv->type = JQVAL_NULL; + break; + default: + qv->type = JQVAL_JBLNODE; + qv->vnode = &unit->json.jn; + break; + } + return unit->json.opaque; + } + case JQP_INTEGER_TYPE: { + if (unit->intval.opaque) { + return (JQVAL*) unit->intval.opaque; + } + JQVAL *qv = iwpool_calloc(sizeof(*qv), aux->pool); + if (!qv) { + *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return 0; + } + unit->intval.opaque = qv; + qv->type = JQVAL_I64; + qv->vi64 = unit->intval.value; + return unit->intval.opaque; + } + case JQP_DOUBLE_TYPE: { + if (unit->dblval.opaque) { + return (JQVAL*) unit->dblval.opaque; + } + JQVAL *qv = iwpool_calloc(sizeof(*qv), aux->pool); + if (!qv) { + *rcp = iwrc_set_errno(IW_ERROR_ALLOC, errno); + return 0; + } + unit->dblval.opaque = qv; + qv->type = JQVAL_F64; + qv->vf64 = unit->dblval.value; + return unit->dblval.opaque; + } + default: + iwlog_ecode_error3(IW_ERROR_ASSERTION); + *rcp = IW_ERROR_ASSERTION; + return 0; + } +} + +JQVAL *jql_unit_to_jqval(JQP_AUX *aux, JQPUNIT *unit, iwrc *rcp) { + return _jql_unit_to_jqval(aux, unit, rcp); +} + +bool jql_jqval_as_int(JQVAL *jqval, int64_t *out) { + switch (jqval->type) { + case JQVAL_I64: + *out = jqval->vi64; + return true; + case JQVAL_STR: + *out = iwatoi(jqval->vstr); + return true; + case JQVAL_F64: + *out = jqval->vf64; + return true; + case JQVAL_BOOL: + *out = jqval->vbool ? 1 : 0; + return true; + case JQVAL_JBLNODE: { + JBL_NODE n = jqval->vnode; + switch (n->type) { + case JBV_I64: + *out = n->vi64; + return true; + case JBV_STR: + *out = iwatoi(jqval->vstr); + return true; + case JBV_F64: + *out = n->vf64; + return true; + case JBV_BOOL: + *out = n->vbool ? 1 : 0; + return true; + default: + *out = 0; + return false; + } + } + default: + *out = 0; + return false; + } +} + +static bool _jql_match_node_expr_impl(MCTX *mctx, JQP_EXPR *expr, iwrc *rcp) { + if (expr->prematched) { + return true; + } + const bool negate = (expr->join && expr->join->negate); + JQPUNIT *left = expr->left; + JQP_OP *op = expr->op; + JQPUNIT *right = expr->right; + if (left->type == JQP_STRING_TYPE) { + if (left->string.flavour & JQP_STR_STAR) { + JQVAL lv, *rv = _jql_unit_to_jqval(mctx->aux, right, rcp); + if (*rcp) { + return false; + } + lv.type = JQVAL_STR; + lv.vstr = mctx->key; + bool ret = _jql_match_jqval_pair(mctx->aux, &lv, op, rv, rcp); + return negate != (0 == !ret); + } else if ( !(left->string.flavour & JQP_STR_DBL_STAR) + && (strcmp(mctx->key, left->string.value) != 0)) { + return negate; + } + } else if (left->type == JQP_EXPR_TYPE) { + if ((left->expr.left->type != JQP_STRING_TYPE) || !(left->expr.left->string.flavour & JQP_STR_STAR)) { + iwlog_ecode_error3(IW_ERROR_ASSERTION); + *rcp = IW_ERROR_ASSERTION; + return false; + } + JQVAL lv, *rv = _jql_unit_to_jqval(mctx->aux, left->expr.right, rcp); + if (*rcp) { + return false; + } + lv.type = JQVAL_STR; + lv.vstr = mctx->key; + if (!_jql_match_jqval_pair(mctx->aux, &lv, left->expr.op, rv, rcp)) { + return negate; + } + } + JQVAL lv, *rv = _jql_unit_to_jqval(mctx->aux, right, rcp); + if (*rcp) { + return false; + } + lv.type = JQVAL_BINN; + lv.vbinn = mctx->bv; + bool ret = _jql_match_jqval_pair(mctx->aux, &lv, expr->op, rv, rcp); + return negate != (0 == !ret); +} + +static bool _jql_match_node_expr(MCTX *mctx, JQP_NODE *n, iwrc *rcp) { + n->start = mctx->lvl; + n->end = n->start; + JQPUNIT *unit = n->value; + if (unit->type != JQP_EXPR_TYPE) { + iwlog_ecode_error3(IW_ERROR_ASSERTION); + *rcp = IW_ERROR_ASSERTION; + return false; + } + bool prev = false; + for (JQP_EXPR *expr = &unit->expr; expr; expr = expr->next) { + bool matched = _jql_match_node_expr_impl(mctx, expr, rcp); + if (*rcp) { + return false; + } + const JQP_JOIN *join = expr->join; + if (!join) { + prev = matched; + } else { + if (join->value == JQP_JOIN_AND) { // AND + prev = prev && matched; + } else if (prev || matched) { // OR + prev = true; + break; + } + } + } + return prev; +} + +IW_INLINE bool _jql_match_node_field(MCTX *mctx, JQP_NODE *n, iwrc *rcp) { + n->start = mctx->lvl; + n->end = n->start; + if (n->value->type != JQP_STRING_TYPE) { + iwlog_ecode_error3(IW_ERROR_ASSERTION); + *rcp = IW_ERROR_ASSERTION; + return false; + } + return (strcmp(n->value->string.value, mctx->key) == 0); +} + +IW_INLINE JQP_NODE *_jql_match_node_anys(MCTX *mctx, JQP_NODE *n, bool *res, iwrc *rcp) { + if (n->start < 0) { + n->start = mctx->lvl; + } + if (n->next) { + JQP_NODE *nn = _jql_match_node(mctx, n->next, res, rcp); + if (*res) { + n->end = -mctx->lvl; // Exclude node from matching + n = nn; + } else { + n->end = INT_MAX; // Gather next level + } + } else { + n->end = INT_MAX; + } + *res = true; + return n; +} + +static JQP_NODE *_jql_match_node(MCTX *mctx, JQP_NODE *n, bool *res, iwrc *rcp) { + switch (n->ntype) { + case JQP_NODE_FIELD: + *res = _jql_match_node_field(mctx, n, rcp); + return n; + case JQP_NODE_EXPR: + *res = _jql_match_node_expr(mctx, n, rcp); + return n; + case JQP_NODE_ANY: + n->start = mctx->lvl; + n->end = n->start; + *res = true; + return n; + case JQP_NODE_ANYS: + return _jql_match_node_anys(mctx, n, res, rcp); + } + return n; +} + +static bool _jql_match_filter(JQP_FILTER *f, MCTX *mctx, iwrc *rcp) { + MFCTX *fctx = f->opaque; + if (fctx->matched) { + return true; + } + bool matched = false; + const int lvl = mctx->lvl; + if (fctx->last_lvl + 1 < lvl) { + return false; + } + if (fctx->last_lvl >= lvl) { + fctx->last_lvl = lvl - 1; + for (JQP_NODE *n = fctx->nodes; n; n = n->next) { + if ((n->start >= lvl) || (-n->end >= lvl)) { + n->start = -1; + n->end = -1; + } + } + } + for (JQP_NODE *n = fctx->nodes; n; n = n->next) { + if ((n->start < 0) || ((lvl >= n->start) && (lvl <= n->end))) { + n = _jql_match_node(mctx, n, &matched, rcp); + if (*rcp) { + return false; + } + if (matched) { + if (n == fctx->last_node) { + fctx->matched = true; + mctx->q->dirty = true; + } + fctx->last_lvl = lvl; + } + break; + } + } + return fctx->matched; +} + +static bool _jql_match_expression_node(JQP_EXPR_NODE *en, MCTX *mctx, iwrc *rcp) { + MENCTX *enctx = en->opaque; + if (enctx->matched) { + return true; + } + bool prev = false; + for (en = en->chain; en; en = en->next) { + bool matched = false; + if (en->type == JQP_EXPR_NODE_TYPE) { + matched = _jql_match_expression_node(en, mctx, rcp); + } else if (en->type == JQP_FILTER_TYPE) { + matched = _jql_match_filter((JQP_FILTER*) en, mctx, rcp); + } + if (*rcp) { + return JBL_VCMD_TERMINATE; + } + const JQP_JOIN *join = en->join; + if (!join) { + prev = matched; + } else { + if (join->negate) { + matched = !matched; + } + if (join->value == JQP_JOIN_AND) { // AND + prev = prev && matched; + } else if (prev || matched) { // OR + prev = true; + break; + } + } + } + return prev; +} + +static jbl_visitor_cmd_t _jql_match_visitor(int lvl, binn *bv, const char *key, int idx, JBL_VCTX *vctx, iwrc *rcp) { + char nbuf[JBNUMBUF_SIZE]; + const char *nkey = key; + JQL q = vctx->op; + if (!nkey) { + iwitoa(idx, nbuf, sizeof(nbuf)); + nkey = nbuf; + } + MCTX mctx = { + .lvl = lvl, + .bv = bv, + .key = nkey, + .vctx = vctx, + .q = q, + .aux = q->aux + }; + q->matched = _jql_match_expression_node(mctx.aux->expr, &mctx, rcp); + if (*rcp || q->matched) { + return JBL_VCMD_TERMINATE; + } + if (q->dirty) { + q->dirty = false; + if (!_jql_need_deeper_match(mctx.aux->expr, lvl)) { + return JBL_VCMD_SKIP_NESTED; + } + } + return 0; +} + +iwrc jql_matched(JQL q, JBL jbl, bool *out) { + JBL_VCTX vctx = { + .bn = &jbl->bn, + .op = q + }; + JQP_EXPR_NODE *en = q->aux->expr; + if (en->flags & JQP_EXPR_NODE_FLAG_PK) { + q->matched = true; + *out = true; + return 0; + } + *out = false; + jql_reset(q, false, false); + if (en->chain && !en->chain->next && !en->next) { + en = en->chain; + if (en->type == JQP_FILTER_TYPE) { + JQP_NODE *n = ((JQP_FILTER*) en)->node; + if (n && ((n->ntype == JQP_NODE_ANYS) || (n->ntype == JQP_NODE_ANY)) && !n->next) { + // Single /* | /** matches anything + q->matched = true; + *out = true; + return 0; + } + } + } + + iwrc rc = _jbl_visit(0, 0, &vctx, _jql_match_visitor); + if (vctx.pool) { + iwpool_destroy(vctx.pool); + } + if (!rc) { + *out = q->matched; + } + return rc; +} + +const char *jql_error(JQL q) { + if (q && q->aux) { + return iwxstr_ptr(q->aux->xerr); + } else { + return ""; + } +} + +const char *jql_first_anchor(JQL q) { + return q->aux->first_anchor; +} + +bool jql_has_apply(JQL q) { + return q->aux->apply || q->aux->apply_placeholder || (q->aux->qmode & (JQP_QRY_APPLY_DEL | JQP_QRY_APPLY_UPSERT)); +} + +bool jql_has_apply_upsert(JQL q) { + return (q->aux->qmode & JQP_QRY_APPLY_UPSERT); +} + +bool jql_has_apply_delete(JQL q) { + return (q->aux->qmode & JQP_QRY_APPLY_DEL); +} + +bool jql_has_projection(JQL q) { + return q->aux->projection; +} + +bool jql_has_orderby(JQL q) { + return q->aux->orderby_num > 0; +} + +bool jql_has_aggregate_count(JQL q) { + return (q->aux->qmode & JQP_QRY_AGGREGATE); +} + +iwrc jql_get_skip(JQL q, int64_t *out) { + iwrc rc = 0; + *out = 0; + struct JQP_AUX *aux = q->aux; + JQPUNIT *skip = aux->skip; + if (!skip) { + return 0; + } + JQVAL *val = _jql_unit_to_jqval(aux, skip, &rc); + RCRET(rc); + if ((val->type != JQVAL_I64) || (val->vi64 < 0)) { // -V522 + return JQL_ERROR_INVALID_PLACEHOLDER; + } + *out = val->vi64; + return 0; +} + +iwrc jql_get_limit(JQL q, int64_t *out) { + iwrc rc = 0; + *out = 0; + struct JQP_AUX *aux = q->aux; + JQPUNIT *limit = aux->limit; + if (!limit) { + return 0; + } + JQVAL *val = _jql_unit_to_jqval(aux, limit, &rc); + RCRET(rc); + if ((val->type != JQVAL_I64) || (val->vi64 < 0)) { // -V522 + return JQL_ERROR_INVALID_PLACEHOLDER; + } + *out = val->vi64; + return 0; +} + +// ----------- JQL Projection + +#define PROJ_MARK_PATH 0x01 +#define PROJ_MARK_KEEP 0x02 +#define PROJ_MARK_FROM_JOIN 0x04 + +typedef struct _PROJ_CTX { + JQL q; + JQP_PROJECTION *proj; + IWPOOL *pool; + JBEXEC *exec_ctx; // Optional! +} PROJ_CTX; + +static void _jql_proj_mark_up(JBL_NODE n, int amask) { + n->flags |= amask; + while ((n = n->parent)) { + n->flags |= PROJ_MARK_PATH; + } +} + +static bool _jql_proj_matched( + int16_t lvl, JBL_NODE n, + const char *key, int keylen, + JBN_VCTX *vctx, JQP_PROJECTION *proj, + iwrc *rc) { + if (proj->cnt <= lvl) { + return false; + } + if (proj->pos >= lvl) { + proj->pos = lvl - 1; + } + if (proj->pos + 1 == lvl) { + JQP_STRING *ps = proj->value; + for (int i = 0; i < lvl; ps = ps->next, ++i) ; // -V529 + assert(ps); + if (ps->flavour & JQP_STR_PROJFIELD) { + for (JQP_STRING *sn = ps; sn; sn = sn->subnext) { + const char *pv = sn->value; + int pvlen = (int) strlen(pv); + if ((pvlen == keylen) && !strncmp(key, pv, keylen)) { + proj->pos = lvl; + return (proj->cnt == lvl + 1); + } + } + } else { + const char *pv = ps->value; + int pvlen = (int) strlen(pv); + if (((pvlen == keylen) && !strncmp(key, pv, keylen)) || ((pv[0] == '*') && (pv[1] == '\0'))) { + proj->pos = lvl; + return (proj->cnt == lvl + 1); + } + } + } + return false; +} + +static bool _jql_proj_join_matched( + int16_t lvl, JBL_NODE n, + const char *key, int keylen, + JBN_VCTX *vctx, JQP_PROJECTION *proj, + JBL *out, + iwrc *rcp) { + + PROJ_CTX *pctx = vctx->op; + if (proj->cnt != lvl + 1) { + return _jql_proj_matched(lvl, n, key, keylen, vctx, proj, rcp); + } + + iwrc rc = 0; + JBL jbl = 0; + const char *pv, *spos; + bool ret = false; + JQP_STRING *ps = proj->value; + for (int i = 0; i < lvl; ps = ps->next, ++i) ; // -V529 + assert(ps); + + if (ps->flavour & JQP_STR_PROJFIELD) { + for (JQP_STRING *sn = ps; sn; sn = sn->subnext) { + pv = sn->value; + spos = strchr(pv, '<'); + if (!spos) { + if ((strlen(pv) == keylen) && !strncmp(key, pv, keylen)) { + proj->pos = lvl; + return true; + } + } + ret = !strncmp(key, pv, spos - pv); + if (ret) { + break; + } + } + } else { + pv = ps->value; + spos = strchr(pv, '<'); + assert(spos); + ret = !strncmp(key, pv, spos - pv); + } + if (ret) { + JBL_NODE nn; + JQVAL jqval; + int64_t id; + JBEXEC *exec_ctx = pctx->exec_ctx; + const char *coll = spos + 1; + if (*coll == '\0') { + return false; + } + jql_node_to_jqval(n, &jqval); + if (!jql_jqval_as_int(&jqval, &id)) { + // Unable to convert current node value as int number + return false; + } + IWSTREE *cache = exec_ctx->proj_joined_nodes_cache; + IWPOOL *pool = exec_ctx->ux->pool; + if (!pool) { + pool = exec_ctx->proj_joined_nodes_pool; + if (!pool) { + pool = iwpool_create(512); + RCGA(pool, finish); + exec_ctx->proj_joined_nodes_pool = pool; + } else if (cache && (iwpool_used_size(pool) > 10 * 1024 * 1024)) { // 10Mb + exec_ctx->proj_joined_nodes_cache = 0; + iwstree_destroy(cache); + cache = 0; + iwpool_destroy(pool); + pool = iwpool_create(1024 * 1024); // 1Mb + RCGA(pool, finish); + } + } + if (!cache) { + cache = iwstree_create(jb_proj_node_cache_cmp, jb_proj_node_kvfree); + RCGA(cache, finish); + exec_ctx->proj_joined_nodes_cache = cache; + } + struct _JBDOCREF ref = { + .id = id, + .coll = coll + }; + nn = iwstree_get(cache, &ref); + if (!nn) { + rc = jb_collection_join_resolver(id, coll, &jbl, exec_ctx); + if (rc) { + if ((rc == IW_ERROR_NOT_EXISTS) || (rc == IWKV_ERROR_NOTFOUND)) { + // If collection is not exists or record is not found just + // keep all untouched + rc = 0; + ret = false; + } + goto finish; + } + RCHECK(rc, finish, jbl_to_node(jbl, &nn, true, pool)); + struct _JBDOCREF *refkey = malloc(sizeof(*refkey)); + RCGA(refkey, finish); + *refkey = ref; + RCHECK(rc, finish, iwstree_put(cache, refkey, nn)); + } + jbn_apply_from(n, nn); + proj->pos = lvl; + } + +finish: + jbl_destroy(&jbl); + *rcp = rc; + return ret; +} + +static jbn_visitor_cmd_t _jql_proj_visitor(int lvl, JBL_NODE n, const char *key, int klidx, JBN_VCTX *vctx, iwrc *rc) { + PROJ_CTX *pctx = vctx->op; + const char *keyptr; + char buf[JBNUMBUF_SIZE]; + if (key) { + keyptr = key; + } else if (lvl < 0) { + return 0; + } else { + iwitoa(klidx, buf, JBNUMBUF_SIZE); + keyptr = buf; + klidx = (int) strlen(keyptr); + } + for (JQP_PROJECTION *p = pctx->proj; p; p = p->next) { + uint8_t flags = p->flags; + JBL jbl = 0; + bool matched; + if (flags & JQP_PROJECTION_FLAG_JOINS) { + matched = _jql_proj_join_matched((int16_t) lvl, n, keyptr, klidx, vctx, p, &jbl, rc); + } else { + matched = _jql_proj_matched((int16_t) lvl, n, keyptr, klidx, vctx, p, rc); + } + RCRET(*rc); + if (matched) { + if (flags & JQP_PROJECTION_FLAG_EXCLUDE) { + return JBN_VCMD_DELETE; + } else if (flags & JQP_PROJECTION_FLAG_INCLUDE) { + _jql_proj_mark_up(n, PROJ_MARK_KEEP); + } else if ((flags & JQP_PROJECTION_FLAG_JOINS) && pctx->q->aux->has_keep_projections) { + _jql_proj_mark_up(n, PROJ_MARK_KEEP | PROJ_MARK_FROM_JOIN); + } + } + } + return 0; +} + +static jbn_visitor_cmd_t _jql_proj_keep_visitor( + int lvl, JBL_NODE n, const char *key, int klidx, JBN_VCTX *vctx, + iwrc *rc) { + if ((lvl < 0) || (n->flags & PROJ_MARK_PATH)) { + return 0; + } + if (n->flags & PROJ_MARK_KEEP) { + return (n->flags & PROJ_MARK_FROM_JOIN) ? JBL_VCMD_OK : JBL_VCMD_SKIP_NESTED; + } + return JBN_VCMD_DELETE; +} + +static iwrc _jql_project(JBL_NODE root, JQL q, IWPOOL *pool, JBEXEC *exec_ctx) { + JQP_AUX *aux = q->aux; + if (aux->has_exclude_all_projection) { + jbn_data(root); + return 0; + } + JQP_PROJECTION *proj = aux->projection; + PROJ_CTX pctx = { + .q = q, + .proj = proj, + .pool = pool, + .exec_ctx = exec_ctx, + }; + if (!pool) { + // No pool no exec_ctx + pctx.exec_ctx = 0; + } + for (JQP_PROJECTION *p = proj; p; p = p->next) { + p->pos = -1; + p->cnt = 0; + for (JQP_STRING *s = p->value; s; s = s->next) { + p->cnt++; + } + } + JBN_VCTX vctx = { + .root = root, + .op = &pctx + }; + iwrc rc = jbn_visit(root, 0, &vctx, _jql_proj_visitor); + RCGO(rc, finish); + if (aux->has_keep_projections) { // We have keep projections + RCHECK(rc, finish, jbn_visit(root, 0, &vctx, _jql_proj_keep_visitor)); + } + +finish: + return rc; +} + +#undef PROJ_MARK_PATH +#undef PROJ_MARK_KEEP + +//---------------------------------- + +iwrc jql_apply(JQL q, JBL_NODE root, IWPOOL *pool) { + if (q->aux->apply_placeholder) { + JQVAL *pv = _jql_find_placeholder(q, q->aux->apply_placeholder); + if (!pv || (pv->type != JQVAL_JBLNODE) || !pv->vnode) { + return JQL_ERROR_INVALID_PLACEHOLDER_VALUE_TYPE; + } + return jbn_patch_auto(root, pv->vnode, pool); + } else if (q->aux->apply) { + return jbn_patch_auto(root, q->aux->apply, pool); + } else { + return 0; + } +} + +iwrc jql_project(JQL q, JBL_NODE root, IWPOOL *pool, void *exec_ctx) { + if (q->aux->projection) { + return _jql_project(root, q, pool, exec_ctx); + } else { + return 0; + } +} + +iwrc jql_apply_and_project(JQL q, JBL jbl, JBL_NODE *out, void *exec_ctx, IWPOOL *pool) { + *out = 0; + JQP_AUX *aux = q->aux; + if (!(aux->apply || aux->apply_placeholder || aux->projection)) { + return 0; + } + JBL_NODE root; + iwrc rc = jbl_to_node(jbl, &root, false, pool); + RCRET(rc); + if (aux->apply || aux->apply_placeholder) { + rc = jql_apply(q, root, pool); + RCRET(rc); + } + if (aux->projection) { + rc = jql_project(q, root, pool, exec_ctx); + } + if (!rc) { + *out = root; + } + return rc; +} + +static const char *_ecodefn(locale_t locale, uint32_t ecode) { + if (!((ecode > _JQL_ERROR_START) && (ecode < _JQL_ERROR_END))) { + return 0; + } + switch (ecode) { + case JQL_ERROR_QUERY_PARSE: + return "Query parsing error (JQL_ERROR_QUERY_PARSE)"; + case JQL_ERROR_INVALID_PLACEHOLDER: + return "Invalid placeholder position (JQL_ERROR_INVALID_PLACEHOLDER)"; + case JQL_ERROR_UNSET_PLACEHOLDER: + return "Found unset placeholder (JQL_ERROR_UNSET_PLACEHOLDER)"; + case JQL_ERROR_REGEXP_INVALID: + return "Invalid regular expression (JQL_ERROR_REGEXP_INVALID)"; + case JQL_ERROR_REGEXP_CHARSET: + return "Invalid regular expression: expected ']' at end of character set (JQL_ERROR_REGEXP_CHARSET)"; + case JQL_ERROR_REGEXP_SUBEXP: + return "Invalid regular expression: expected ')' at end of subexpression (JQL_ERROR_REGEXP_SUBEXP)"; + case JQL_ERROR_REGEXP_SUBMATCH: + return "Invalid regular expression: expected '}' at end of submatch (JQL_ERROR_REGEXP_SUBMATCH)"; + case JQL_ERROR_REGEXP_ENGINE: + return "Illegal instruction in compiled regular expression (please report this bug) (JQL_ERROR_REGEXP_ENGINE)"; + case JQL_ERROR_SKIP_ALREADY_SET: + return "Skip clause already specified (JQL_ERROR_SKIP_ALREADY_SET)"; + case JQL_ERROR_LIMIT_ALREADY_SET: + return "Limit clause already specified (JQL_ERROR_SKIP_ALREADY_SET)"; + case JQL_ERROR_ORDERBY_MAX_LIMIT: + return "Reached max number of asc/desc order clauses: 64 (JQL_ERROR_ORDERBY_MAX_LIMIT)"; + case JQL_ERROR_NO_COLLECTION: + return "No collection specified in query (JQL_ERROR_NO_COLLECTION)"; + case JQL_ERROR_INVALID_PLACEHOLDER_VALUE_TYPE: + return "Invalid type of placeholder value (JQL_ERROR_INVALID_PLACEHOLDER_VALUE_TYPE)"; + default: + break; + } + return 0; +} + +iwrc jql_init() { + static int _jql_initialized = 0; + if (!__sync_bool_compare_and_swap(&_jql_initialized, 0, 1)) { + return 0; + } + return iwlog_register_ecodefn(_ecodefn); +} diff --git a/src/jql/jql.h b/src/jql/jql.h new file mode 100644 index 0000000..e3cf1c3 --- /dev/null +++ b/src/jql/jql.h @@ -0,0 +1,184 @@ +#pragma once +#ifndef JQL_H +#define JQL_H + +/************************************************************************************************** + * EJDB2 + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +/** @file + * + * @brief EJDB query construction API. + * + * EJDB query syntax inpired by ideas behind XPath and Unix shell pipes. + * + * + */ + +#include "jbl.h" +#include + +IW_EXTERN_C_START + +struct _JQL; +typedef struct _JQL*JQL; + +typedef enum { + _JQL_ERROR_START = (IW_ERROR_START + 15000UL + 2000), + JQL_ERROR_QUERY_PARSE, /**< Query parsing error (JQL_ERROR_QUERY_PARSE) */ + JQL_ERROR_INVALID_PLACEHOLDER, /**< Invalid placeholder position (JQL_ERROR_INVALID_PLACEHOLDER) */ + JQL_ERROR_UNSET_PLACEHOLDER, /**< Found unset placeholder (JQL_ERROR_UNSET_PLACEHOLDER) */ + JQL_ERROR_REGEXP_INVALID, /**< Invalid regular expression (JQL_ERROR_REGEXP_INVALID) */ + JQL_ERROR_REGEXP_CHARSET, + /**< Invalid regular expression: expected ']' at end of character set + (JQL_ERROR_REGEXP_CHARSET) */ + JQL_ERROR_REGEXP_SUBEXP, + /**< Invalid regular expression: expected ')' at end of subexpression + (JQL_ERROR_REGEXP_SUBEXP) */ + JQL_ERROR_REGEXP_SUBMATCH, + /**< Invalid regular expression: expected '}' at end of submatch + (JQL_ERROR_REGEXP_SUBMATCH) */ + JQL_ERROR_REGEXP_ENGINE, + /**< Illegal instruction in compiled regular expression (please report this + bug) (JQL_ERROR_REGEXP_ENGINE) */ + JQL_ERROR_SKIP_ALREADY_SET, /**< Skip clause already specified (JQL_ERROR_SKIP_ALREADY_SET) */ + JQL_ERROR_LIMIT_ALREADY_SET, /**< Limit clause already specified (JQL_ERROR_SKIP_ALREADY_SET) */ + JQL_ERROR_ORDERBY_MAX_LIMIT, + /**< Reached max number of asc/desc order clauses: 64 + (JQL_ERROR_ORDERBY_MAX_LIMIT) */ + JQL_ERROR_NO_COLLECTION, /**< No collection specified in query (JQL_ERROR_NO_COLLECTION) */ + JQL_ERROR_INVALID_PLACEHOLDER_VALUE_TYPE, + /**< Invalid type of placeholder value + (JQL_ERROR_INVALID_PLACEHOLDER_VALUE_TYPE) */ + _JQL_ERROR_END, + _JQL_ERROR_UNMATCHED, +} jql_ecode_t; + +typedef uint8_t jql_create_mode_t; + +/// Do not destroy query object in `jql_create()` on query parsing error, +/// convenient for parsing error reporting using `jql_error()` +#define JQL_KEEP_QUERY_ON_PARSE_ERROR ((jql_create_mode_t) 0x01U) + +/// Do not print query parsing errors to `stderr` +#define JQL_SILENT_ON_PARSE_ERROR ((jql_create_mode_t) 0x02U) + +/** + * @brief Create query object from specified text query. + * @param qptr Pointer to resulting query object + * @param coll Optional collection name used to execute query + * @param query Query text + */ +IW_EXPORT WUR iwrc jql_create(JQL *qptr, const char *coll, const char *query); + +IW_EXPORT WUR iwrc jql_create2(JQL *qptr, const char *coll, const char *query, jql_create_mode_t mode); + +IW_EXPORT const char *jql_collection(JQL q); + +/** + * @brief Bind JSON node data to query placeholder. + * @warning Value JSON data is not copied and used as is. + * Caller is responsible to maintain `val` availability during execution of query. + * @see jql_set_json2() + */ +IW_EXPORT WUR iwrc jql_set_json(JQL q, const char *placeholder, int index, JBL_NODE val); + +IW_EXPORT WUR iwrc jql_set_json2( + JQL q, const char *placeholder, int index, JBL_NODE val, + void (*freefn)(void*, void*), void *op); + +IW_EXPORT WUR iwrc jql_set_json_jbl(JQL q, const char *placeholder, int index, JBL jbl); + +IW_EXPORT WUR iwrc jql_set_i64(JQL q, const char *placeholder, int index, int64_t val); + +IW_EXPORT WUR iwrc jql_set_f64(JQL q, const char *placeholder, int index, double val); + +/** + * @brief Bind string data to query placeholder. + * @warning Value string data is not copied and used as is. + * Caller is responsible to maintain `val` availability during execution of query. + * @see jql_set_str2() + */ +IW_EXPORT WUR iwrc jql_set_str(JQL q, const char *placeholder, int index, const char *val); + +IW_EXPORT WUR iwrc jql_set_str2( + JQL q, const char *placeholder, int index, const char *val, + void (*freefn)(void*, void*), void *op); + +IW_EXPORT WUR iwrc jql_set_bool(JQL q, const char *placeholder, int index, bool val); + + +/** + * @brief Bind regexp data string to query placeholder. + * @warning Value string data is not copied and used as is. + * Caller is responsible to maintain `val` availability during execution of query. + * @see jql_set_regexp2() + */ +IW_EXPORT WUR iwrc jql_set_regexp(JQL q, const char *placeholder, int index, const char *expr); + +IW_EXPORT WUR iwrc jql_set_regexp2( + JQL q, const char *placeholder, int index, const char *expr, + void (*freefn)(void*, void*), void *op); + +IW_EXPORT WUR iwrc jql_set_null(JQL q, const char *placeholder, int index); + +IW_EXPORT WUR iwrc jql_matched(JQL q, JBL jbl, bool *out); + +IW_EXPORT const char *jql_first_anchor(JQL q); + +IW_EXPORT const char *jql_error(JQL q); + +IW_EXPORT bool jql_has_apply(JQL q); + +IW_EXPORT bool jql_has_apply_upsert(JQL q); + +IW_EXPORT bool jql_has_apply_delete(JQL q); + +IW_EXPORT bool jql_has_projection(JQL q); + +IW_EXPORT bool jql_has_orderby(JQL q); + +IW_EXPORT bool jql_has_aggregate_count(JQL q); + +IW_EXPORT iwrc jql_get_skip(JQL q, int64_t *out); + +IW_EXPORT iwrc jql_get_limit(JQL q, int64_t *out); + +IW_EXPORT WUR iwrc jql_apply(JQL q, JBL_NODE root, IWPOOL *pool); + +IW_EXPORT WUR iwrc jql_project(JQL q, JBL_NODE root, IWPOOL *pool, void *exec_ctx); + +IW_EXPORT WUR iwrc jql_apply_and_project(JQL q, JBL jbl, JBL_NODE *out, void *exec_ctx, IWPOOL *pool); + +IW_EXPORT void jql_reset(JQL q, bool reset_match_cache, bool reset_placeholders); + +IW_EXPORT void jql_destroy(JQL *qptr); + +IW_EXPORT size_t jql_estimate_allocated_size(JQL q); + +IW_EXPORT WUR iwrc jql_init(void); + +IW_EXTERN_C_END +#endif diff --git a/src/jql/jql_internal.h b/src/jql/jql_internal.h new file mode 100644 index 0000000..bb5fd3e --- /dev/null +++ b/src/jql/jql_internal.h @@ -0,0 +1,62 @@ +#pragma once +#ifndef JQL_INTERNAL_H +#define JQL_INTERNAL_H + +#include "jqp.h" +#include "binn.h" +#include + + +/** Query object */ +struct _JQL { + bool dirty; + bool matched; + JQP_QUERY *qp; + JQP_AUX *aux; + const char *coll; + void *opaque; +}; + +/** Placeholder value type */ +typedef enum { + JQVAL_NULL, // Do not reorder + JQVAL_I64, + JQVAL_F64, + JQVAL_STR, + JQVAL_BOOL, + JQVAL_RE, + JQVAL_JBLNODE, // Do not reorder JQVAL_JBLNODE,JQVAL_BINN must be last + JQVAL_BINN, +} jqval_type_t; + +/** Placeholder value */ +typedef struct { + jqval_type_t type; + void (*freefn)(void*, void*); + void *freefn_op; + union { + JBL_NODE vnode; + binn *vbinn; + int64_t vi64; + double vf64; + const char *vstr; + bool vbool; + struct re *vre; + }; +} JQVAL; + +JQVAL *jql_find_placeholder(JQL q, const char *name); + +JQVAL *jql_unit_to_jqval(JQP_AUX *aux, JQPUNIT *unit, iwrc *rcp); + +bool jql_jqval_as_int(JQVAL *jqval, int64_t *out); + +jqval_type_t jql_binn_to_jqval(binn *vbinn, JQVAL *qval); + +void jql_node_to_jqval(JBL_NODE jn, JQVAL *qv); + +int jql_cmp_jqval_pair(const JQVAL *left, const JQVAL *right, iwrc *rcp); + +bool jql_match_jqval_pair(JQP_AUX *aux, JQVAL *left, JQP_OP *jqop, JQVAL *right, iwrc *rcp); + +#endif diff --git a/src/jql/jqp.c b/src/jql/jqp.c new file mode 100644 index 0000000..17abc3e --- /dev/null +++ b/src/jql/jqp.c @@ -0,0 +1,3168 @@ +/* A recursive-descent parser generated by peg 0.1.18 */ + +#include +#include +#include +#define YYRULECOUNT 63 +#line 1 "./jqp.leg" + +#include "jqp.h" +#include "jbl.h" + + +#define YY_CTX_LOCAL 1 +#define YY_CTX_MEMBERS \ + JQP_AUX *aux; + +struct _yycontext; + +static void _jqp_finish(struct _yycontext *yy); +static void _jqp_debug(struct _yycontext *yy, const char *text); +static void *_jqp_malloc(struct _yycontext *yy, size_t size); +static void *_jqp_realloc(struct _yycontext *yy, void *ptr, size_t size); +static JQPUNIT *_jqp_unit(struct _yycontext *yy); + +static void _jqp_op_negate(struct _yycontext *yy); +static void _jqp_op_negate_reset(struct _yycontext *yy); + +static JQPUNIT *_jqp_string(struct _yycontext *yy, jqp_string_flavours_t flv, const char *text); +static JQPUNIT *_jqp_unescaped_string(struct _yycontext *yy, jqp_string_flavours_t flv, const char *text); +static JQPUNIT *_jqp_number(struct _yycontext *yy, jqp_int_flavours_t flv, const char *text); +static JQPUNIT *_jqp_placeholder(struct _yycontext *yy, const char *text); +static JQPUNIT *_jqp_unit_op(struct _yycontext *yy, const char *text); +static JQPUNIT *_jqp_unit_join(struct _yycontext *yy, const char *text); +static JQPUNIT *_jqp_expr(struct _yycontext *yy, JQPUNIT *left, JQPUNIT *op, JQPUNIT *right); +static JQPUNIT *_jqp_node(struct _yycontext *yy, JQPUNIT *value); +static JQPUNIT *_jqp_projection(struct _yycontext *yy, JQPUNIT *value, uint8_t flags); +static void _jqp_set_skip(struct _yycontext *yy, JQPUNIT *unit); +static void _jqp_set_limit(struct _yycontext *yy, JQPUNIT *unit); +static void _jqp_add_orderby(struct _yycontext *yy, JQPUNIT *unit); +static void _jqp_set_aggregate_count(struct _yycontext *yy); +static void _jqp_set_noidx(struct _yycontext *yy); +static void _jqp_set_inverse(struct _yycontext *yy); + +static JQPUNIT *_jqp_json_string(struct _yycontext *yy, const char *text); +static JQPUNIT *_jqp_json_number(struct _yycontext *yy, const char *text); +static JQPUNIT *_jqp_json_true_false_null(struct _yycontext *yy, const char *text); +static JQPUNIT *_jqp_json_pair(struct _yycontext *yy, JQPUNIT *key, JQPUNIT *val); +static JQPUNIT *_jqp_json_collect(struct _yycontext *yy, jbl_type_t type, JQPUNIT *until); + +static JQP_STACK *_jqp_push(struct _yycontext *yy); +static void _jqp_unit_push(struct _yycontext *yy, JQPUNIT *unit); +static JQPUNIT *_jqp_unit_pop(struct _yycontext *yy); +static void _jqp_string_push(struct _yycontext *yy, char *str, bool dup); +static char *_jqp_string_pop(struct _yycontext *yy); + +static JQPUNIT *_jqp_pop_filter_factor_chain(struct _yycontext *yy, JQPUNIT *until); +static JQPUNIT *_jqp_pop_expr_chain(struct _yycontext *yy, JQPUNIT *until); +static JQPUNIT *_jqp_pop_node_chain(struct _yycontext *yy, JQPUNIT *until); +static JQPUNIT *_jqp_pop_projfields_chain(struct _yycontext *yy, JQPUNIT *until); +static JQPUNIT *_jqp_pop_projection_nodes(struct _yycontext *yy, JQPUNIT *until); +static JQPUNIT *_jqp_pop_ordernodes(struct _yycontext *yy, JQPUNIT *until); +static JQPUNIT *_jqp_push_joined_projection(struct _yycontext *yy, JQPUNIT *p); +static JQPUNIT *_jqp_pop_joined_projections(struct _yycontext *yy, JQPUNIT *until); + +static void _jqp_set_filters_expr(struct _yycontext *yy, JQPUNIT *expr); +static JQPUNIT *_jqp_create_filterexpr_pk(struct _yycontext *yy, JQPUNIT *argument); +static void _jqp_set_apply(struct _yycontext *yy, JQPUNIT *unit); +static void _jqp_set_apply_delete(struct _yycontext *yy); +static void _jqp_set_apply_upsert(struct _yycontext *yy, JQPUNIT *unit); +static void _jqp_set_projection(struct _yycontext *yy, JQPUNIT *unit); + +#define YYSTYPE JQPUNIT* +#define YY_MALLOC(yy_, sz_) _jqp_malloc(yy_, sz_) +#define YY_REALLOC(yy_, ptr_, sz_) _jqp_realloc(yy_, ptr_, sz_) + +#define YY_INPUT(yy_, buf_, result_, max_size_) \ + { \ + JQP_AUX *aux = (yy_)->aux; \ + if (aux->rc || *(aux->buf + aux->pos) == '\0') { \ + result_ = 0; \ + } else { \ + char ch = *(aux->buf + aux->pos++); \ + result_ = 1; \ + *(buf_)= ch; \ + } \ + } + +#ifndef YY_MALLOC +#define YY_MALLOC(C, N) malloc(N) +#endif +#ifndef YY_REALLOC +#define YY_REALLOC(C, P, N) realloc(P, N) +#endif +#ifndef YY_FREE +#define YY_FREE(C, P) free(P) +#endif +#ifndef YY_LOCAL +#define YY_LOCAL(T) static T +#endif +#ifndef YY_ACTION +#define YY_ACTION(T) static T +#endif +#ifndef YY_RULE +#define YY_RULE(T) static T +#endif +#ifndef YY_PARSE +#define YY_PARSE(T) T +#endif +#ifndef YYPARSE +#define YYPARSE yyparse +#endif +#ifndef YYPARSEFROM +#define YYPARSEFROM yyparsefrom +#endif +#ifndef YYRELEASE +#define YYRELEASE yyrelease +#endif +#ifndef YY_BEGIN +#define YY_BEGIN ( yy->__begin= yy->__pos, 1) +#endif +#ifndef YY_END +#define YY_END ( yy->__end= yy->__pos, 1) +#endif +#ifdef YY_DEBUG +# define yyprintf(args) fprintf args +#else +# define yyprintf(args) +#endif +#ifndef YYSTYPE +#define YYSTYPE int +#endif +#ifndef YY_STACK_SIZE +#define YY_STACK_SIZE 128 +#endif + +#ifndef YY_BUFFER_SIZE +#define YY_BUFFER_SIZE 1024 +#endif + +#ifndef YY_PART + +typedef struct _yycontext yycontext; +typedef void (*yyaction)(yycontext *yy, char *yytext, int yyleng); +typedef struct _yythunk { int begin, end; yyaction action; struct _yythunk *next; } yythunk; + +struct _yycontext { + char *__buf; + int __buflen; + int __pos; + int __limit; + char *__text; + int __textlen; + int __begin; + int __end; + int __textmax; + yythunk *__thunks; + int __thunkslen; + int __thunkpos; + YYSTYPE __; + YYSTYPE *__val; + YYSTYPE *__vals; + int __valslen; +#ifdef YY_CTX_MEMBERS + YY_CTX_MEMBERS +#endif +}; + +#ifdef YY_CTX_LOCAL +#define YY_CTX_PARAM_ yycontext *yyctx, +#define YY_CTX_PARAM yycontext *yyctx +#define YY_CTX_ARG_ yyctx, +#define YY_CTX_ARG yyctx +#ifndef YY_INPUT +#define YY_INPUT(yy, buf, result, max_size) \ + { \ + int yyc= getchar(); \ + result= (EOF == yyc) ? 0 : (*(buf)= yyc, 1); \ + yyprintf((stderr, "<%c>", yyc)); \ + } +#endif +#else +#define YY_CTX_PARAM_ +#define YY_CTX_PARAM +#define YY_CTX_ARG_ +#define YY_CTX_ARG +yycontext _yyctx= { 0, 0 }; +yycontext *yyctx= &_yyctx; +#ifndef YY_INPUT +#define YY_INPUT(buf, result, max_size) \ + { \ + int yyc= getchar(); \ + result= (EOF == yyc) ? 0 : (*(buf)= yyc, 1); \ + yyprintf((stderr, "<%c>", yyc)); \ + } +#endif +#endif + +YY_LOCAL(int) yyrefill(yycontext *yy) +{ + int yyn; + while (yy->__buflen - yy->__pos < 512) + { + yy->__buflen *= 2; + yy->__buf= (char *)YY_REALLOC(yy, yy->__buf, yy->__buflen); + } +#ifdef YY_CTX_LOCAL + YY_INPUT(yy, (yy->__buf + yy->__pos), yyn, (yy->__buflen - yy->__pos)); +#else + YY_INPUT((yy->__buf + yy->__pos), yyn, (yy->__buflen - yy->__pos)); +#endif + if (!yyn) return 0; + yy->__limit += yyn; + return 1; +} + +YY_LOCAL(int) yymatchDot(yycontext *yy) +{ + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0; + ++yy->__pos; + return 1; +} + +YY_LOCAL(int) yymatchChar(yycontext *yy, int c) +{ + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0; + if ((unsigned char)yy->__buf[yy->__pos] == c) + { + ++yy->__pos; + yyprintf((stderr, " ok yymatchChar(yy, %c) @ %s\n", c, yy->__buf+yy->__pos)); + return 1; + } + yyprintf((stderr, " fail yymatchChar(yy, %c) @ %s\n", c, yy->__buf+yy->__pos)); + return 0; +} + +YY_LOCAL(int) yymatchString(yycontext *yy, const char *s) +{ + int yysav= yy->__pos; + while (*s) + { + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0; + if (yy->__buf[yy->__pos] != *s) + { + yy->__pos= yysav; + return 0; + } + ++s; + ++yy->__pos; + } + return 1; +} + +YY_LOCAL(int) yymatchClass(yycontext *yy, unsigned char *bits) +{ + int c; + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0; + c= (unsigned char)yy->__buf[yy->__pos]; + if (bits[c >> 3] & (1 << (c & 7))) + { + ++yy->__pos; + yyprintf((stderr, " ok yymatchClass @ %s\n", yy->__buf+yy->__pos)); + return 1; + } + yyprintf((stderr, " fail yymatchClass @ %s\n", yy->__buf+yy->__pos)); + return 0; +} + +YY_LOCAL(void) yyDo(yycontext *yy, yyaction action, int begin, int end) +{ + while (yy->__thunkpos >= yy->__thunkslen) + { + yy->__thunkslen *= 2; + yy->__thunks= (yythunk *)YY_REALLOC(yy, yy->__thunks, sizeof(yythunk) * yy->__thunkslen); + } + yy->__thunks[yy->__thunkpos].begin= begin; + yy->__thunks[yy->__thunkpos].end= end; + yy->__thunks[yy->__thunkpos].action= action; + ++yy->__thunkpos; +} + +YY_LOCAL(int) yyText(yycontext *yy, int begin, int end) +{ + int yyleng= end - begin; + if (yyleng <= 0) + yyleng= 0; + else + { + while (yy->__textlen < (yyleng + 1)) + { + yy->__textlen *= 2; + yy->__text= (char *)YY_REALLOC(yy, yy->__text, yy->__textlen); + } + memcpy(yy->__text, yy->__buf + begin, yyleng); + } + yy->__text[yyleng]= '\0'; + return yyleng; +} + +YY_LOCAL(void) yyDone(yycontext *yy) +{ + int pos; + for (pos= 0; pos < yy->__thunkpos; ++pos) + { + yythunk *thunk= &yy->__thunks[pos]; + int yyleng= thunk->end ? yyText(yy, thunk->begin, thunk->end) : thunk->begin; + yyprintf((stderr, "DO [%d] %p %s\n", pos, thunk->action, yy->__text)); + thunk->action(yy, yy->__text, yyleng); + } + yy->__thunkpos= 0; +} + +YY_LOCAL(void) yyCommit(yycontext *yy) +{ + if ((yy->__limit -= yy->__pos)) + { + memmove(yy->__buf, yy->__buf + yy->__pos, yy->__limit); + } + yy->__begin -= yy->__pos; + yy->__end -= yy->__pos; + yy->__pos= yy->__thunkpos= 0; +} + +YY_LOCAL(int) yyAccept(yycontext *yy, int tp0) +{ + if (tp0) + { + fprintf(stderr, "accept denied at %d\n", tp0); + return 0; + } + else + { + yyDone(yy); + yyCommit(yy); + } + return 1; +} + +YY_LOCAL(void) yyPush(yycontext *yy, char *text, int count) +{ + yy->__val += count; + while (yy->__valslen <= yy->__val - yy->__vals) + { + long offset= yy->__val - yy->__vals; + yy->__valslen *= 2; + yy->__vals= (YYSTYPE *)YY_REALLOC(yy, yy->__vals, sizeof(YYSTYPE) * yy->__valslen); + yy->__val= yy->__vals + offset; + } +} +YY_LOCAL(void) yyPop(yycontext *yy, char *text, int count) { yy->__val -= count; } +YY_LOCAL(void) yySet(yycontext *yy, char *text, int count) { yy->__val[count]= yy->__; } + +#endif /* YY_PART */ + +#define YYACCEPT yyAccept(yy, yythunkpos0) + +YY_RULE(int) yy_EOL(yycontext *yy); /* 63 */ +YY_RULE(int) yy_SPACE(yycontext *yy); /* 62 */ +YY_RULE(int) yy_NUME(yycontext *yy); /* 61 */ +YY_RULE(int) yy_NUMF(yycontext *yy); /* 60 */ +YY_RULE(int) yy_NUMJ(yycontext *yy); /* 59 */ +YY_RULE(int) yy_STRJ(yycontext *yy); /* 58 */ +YY_RULE(int) yy_SARRJ(yycontext *yy); /* 57 */ +YY_RULE(int) yy_PAIRJ(yycontext *yy); /* 56 */ +YY_RULE(int) yy_SOBJJ(yycontext *yy); /* 55 */ +YY_RULE(int) yy_CHJ(yycontext *yy); /* 54 */ +YY_RULE(int) yy_CHP(yycontext *yy); /* 53 */ +YY_RULE(int) yy_VALJ(yycontext *yy); /* 52 */ +YY_RULE(int) yy_NEXPRLEFT(yycontext *yy); /* 51 */ +YY_RULE(int) yy_STRSTAR(yycontext *yy); /* 50 */ +YY_RULE(int) yy_DBLSTAR(yycontext *yy); /* 49 */ +YY_RULE(int) yy_NEXRIGHT(yycontext *yy); /* 48 */ +YY_RULE(int) yy_NEXOP(yycontext *yy); /* 47 */ +YY_RULE(int) yy_NEXLEFT(yycontext *yy); /* 46 */ +YY_RULE(int) yy_NEXJOIN(yycontext *yy); /* 45 */ +YY_RULE(int) yy_NEXPAIR(yycontext *yy); /* 44 */ +YY_RULE(int) yy_STRP(yycontext *yy); /* 43 */ +YY_RULE(int) yy_NEXPR(yycontext *yy); /* 42 */ +YY_RULE(int) yy_NODE(yycontext *yy); /* 41 */ +YY_RULE(int) yy_FILTER(yycontext *yy); /* 40 */ +YY_RULE(int) yy_FILTERFACTOR(yycontext *yy); /* 39 */ +YY_RULE(int) yy_HEX(yycontext *yy); /* 38 */ +YY_RULE(int) yy_PCHP(yycontext *yy); /* 37 */ +YY_RULE(int) yy_PSTRP(yycontext *yy); /* 36 */ +YY_RULE(int) yy_STRN(yycontext *yy); /* 35 */ +YY_RULE(int) yy_PROJFIELDS(yycontext *yy); /* 34 */ +YY_RULE(int) yy_PROJNODE(yycontext *yy); /* 33 */ +YY_RULE(int) yy_PROJALL(yycontext *yy); /* 32 */ +YY_RULE(int) yy_PROJPROP(yycontext *yy); /* 31 */ +YY_RULE(int) yy_ORDERNODE(yycontext *yy); /* 30 */ +YY_RULE(int) yy_ORDERNODES(yycontext *yy); /* 29 */ +YY_RULE(int) yy_NUMI(yycontext *yy); /* 28 */ +YY_RULE(int) yy_INVERSE(yycontext *yy); /* 27 */ +YY_RULE(int) yy_NOIDX(yycontext *yy); /* 26 */ +YY_RULE(int) yy_COUNT(yycontext *yy); /* 25 */ +YY_RULE(int) yy_ORDERBY(yycontext *yy); /* 24 */ +YY_RULE(int) yy_LIMIT(yycontext *yy); /* 23 */ +YY_RULE(int) yy_SKIP(yycontext *yy); /* 22 */ +YY_RULE(int) yy_OPT(yycontext *yy); /* 21 */ +YY_RULE(int) yy_PROJOIN(yycontext *yy); /* 20 */ +YY_RULE(int) yy_PROJNODES(yycontext *yy); /* 19 */ +YY_RULE(int) yy_ARRJ(yycontext *yy); /* 18 */ +YY_RULE(int) yy_OBJJ(yycontext *yy); /* 17 */ +YY_RULE(int) yy___(yycontext *yy); /* 16 */ +YY_RULE(int) yy_FILTERJOIN(yycontext *yy); /* 15 */ +YY_RULE(int) yy_NUMPK_ARR(yycontext *yy); /* 14 */ +YY_RULE(int) yy_NUMPK(yycontext *yy); /* 13 */ +YY_RULE(int) yy_PLACEHOLDER(yycontext *yy); /* 12 */ +YY_RULE(int) yy_FILTERANCHOR(yycontext *yy); /* 11 */ +YY_RULE(int) yy_FILTEREXPR(yycontext *yy); /* 10 */ +YY_RULE(int) yy_FILTEREXPR_PK(yycontext *yy); /* 9 */ +YY_RULE(int) yy_EOF(yycontext *yy); /* 8 */ +YY_RULE(int) yy_OPTS(yycontext *yy); /* 7 */ +YY_RULE(int) yy_PROJECTION(yycontext *yy); /* 6 */ +YY_RULE(int) yy_UPSERT(yycontext *yy); /* 5 */ +YY_RULE(int) yy_APPLY(yycontext *yy); /* 4 */ +YY_RULE(int) yy__(yycontext *yy); /* 3 */ +YY_RULE(int) yy_QEXPR(yycontext *yy); /* 2 */ +YY_RULE(int) yy_QUERY(yycontext *yy); /* 1 */ + +YY_ACTION(void) yy_4_NUMPK_ARR(yycontext *yy, char *yytext, int yyleng) +{ +#define v yy->__val[-1] +#define fv yy->__val[-2] +#define s yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_4_NUMPK_ARR\n")); + { +#line 235 + __ = _jqp_json_collect(yy, JBV_ARRAY, s); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef v +#undef fv +#undef s +} +YY_ACTION(void) yy_3_NUMPK_ARR(yycontext *yy, char *yytext, int yyleng) +{ +#define v yy->__val[-1] +#define fv yy->__val[-2] +#define s yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_NUMPK_ARR\n")); + { +#line 234 + _jqp_unit_push(yy, v); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef v +#undef fv +#undef s +} +YY_ACTION(void) yy_2_NUMPK_ARR(yycontext *yy, char *yytext, int yyleng) +{ +#define v yy->__val[-1] +#define fv yy->__val[-2] +#define s yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_NUMPK_ARR\n")); + { +#line 234 + _jqp_unit_push(yy, fv); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef v +#undef fv +#undef s +} +YY_ACTION(void) yy_1_NUMPK_ARR(yycontext *yy, char *yytext, int yyleng) +{ +#define v yy->__val[-1] +#define fv yy->__val[-2] +#define s yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_NUMPK_ARR\n")); + { +#line 233 + _jqp_unit_push(yy, s); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef v +#undef fv +#undef s +} +YY_ACTION(void) yy_1_NUMPK(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_NUMPK\n")); + { +#line 231 + __ = _jqp_json_number(yy, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_NUMJ(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_NUMJ\n")); + { +#line 229 + __ = _jqp_json_number(yy, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_STRJ(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_STRJ\n")); + { +#line 214 + __ = _jqp_json_string(yy, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_3_VALJ(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_VALJ\n")); + { +#line 212 + __ = _jqp_json_true_false_null(yy, "null"); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_2_VALJ(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_VALJ\n")); + { +#line 211 + __ = _jqp_json_true_false_null(yy, "false"); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_VALJ(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_VALJ\n")); + { +#line 210 + __ = _jqp_json_true_false_null(yy, "true"); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_PAIRJ(yycontext *yy, char *yytext, int yyleng) +{ +#define v yy->__val[-1] +#define s yy->__val[-2] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_PAIRJ\n")); + { +#line 204 + __ = _jqp_json_pair(yy, s, v); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef v +#undef s +} +YY_ACTION(void) yy_1_SARRJ(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_SARRJ\n")); + { +#line 202 + __ = _jqp_unit(yy); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_SOBJJ(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_SOBJJ\n")); + { +#line 200 + __ = _jqp_unit(yy); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_4_ARRJ(yycontext *yy, char *yytext, int yyleng) +{ +#define v yy->__val[-1] +#define fv yy->__val[-2] +#define s yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_4_ARRJ\n")); + { +#line 198 + __ = _jqp_json_collect(yy, JBV_ARRAY, s); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef v +#undef fv +#undef s +} +YY_ACTION(void) yy_3_ARRJ(yycontext *yy, char *yytext, int yyleng) +{ +#define v yy->__val[-1] +#define fv yy->__val[-2] +#define s yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_ARRJ\n")); + { +#line 197 + _jqp_unit_push(yy, v); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef v +#undef fv +#undef s +} +YY_ACTION(void) yy_2_ARRJ(yycontext *yy, char *yytext, int yyleng) +{ +#define v yy->__val[-1] +#define fv yy->__val[-2] +#define s yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_ARRJ\n")); + { +#line 197 + _jqp_unit_push(yy, fv); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef v +#undef fv +#undef s +} +YY_ACTION(void) yy_1_ARRJ(yycontext *yy, char *yytext, int yyleng) +{ +#define v yy->__val[-1] +#define fv yy->__val[-2] +#define s yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_ARRJ\n")); + { +#line 196 + _jqp_unit_push(yy, s); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef v +#undef fv +#undef s +} +YY_ACTION(void) yy_4_OBJJ(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define fp yy->__val[-2] +#define s yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_4_OBJJ\n")); + { +#line 194 + __ = _jqp_json_collect(yy, JBV_OBJECT, s); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +#undef fp +#undef s +} +YY_ACTION(void) yy_3_OBJJ(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define fp yy->__val[-2] +#define s yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_OBJJ\n")); + { +#line 193 + _jqp_unit_push(yy, p); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +#undef fp +#undef s +} +YY_ACTION(void) yy_2_OBJJ(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define fp yy->__val[-2] +#define s yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_OBJJ\n")); + { +#line 193 + _jqp_unit_push(yy, fp); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +#undef fp +#undef s +} +YY_ACTION(void) yy_1_OBJJ(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define fp yy->__val[-2] +#define s yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_OBJJ\n")); + { +#line 192 + _jqp_unit_push(yy, s); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +#undef fp +#undef s +} +YY_ACTION(void) yy_1_STRN(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_STRN\n")); + { +#line 190 + __ = _jqp_unescaped_string(yy, JQP_STR_QUOTED, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_STRSTAR(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_STRSTAR\n")); + { +#line 188 + __ = _jqp_unescaped_string(yy, JQP_STR_STAR, "*"); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_DBLSTAR(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_DBLSTAR\n")); + { +#line 186 + __ = _jqp_unescaped_string(yy, JQP_STR_DBL_STAR, "**"); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_STRP(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_STRP\n")); + { +#line 184 + __ = _jqp_unescaped_string(yy, 0, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_9_NEXOP(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_9_NEXOP\n")); + { +#line 182 + __ = _jqp_unit_op(yy, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_8_NEXOP(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_8_NEXOP\n")); + { +#line 181 + __ = _jqp_unit_op(yy, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_7_NEXOP(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_7_NEXOP\n")); + { +#line 180 + __ = _jqp_unit_op(yy, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_6_NEXOP(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_6_NEXOP\n")); + { +#line 179 + __ = _jqp_unit_op(yy, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_5_NEXOP(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_5_NEXOP\n")); + { +#line 179 + _jqp_op_negate(yy); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_4_NEXOP(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_4_NEXOP\n")); + { +#line 178 + __ = _jqp_unit_op(yy, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_3_NEXOP(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_NEXOP\n")); + { +#line 177 + __ = _jqp_unit_op(yy, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_2_NEXOP(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_NEXOP\n")); + { +#line 176 + __ = _jqp_unit_op(yy, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_NEXOP(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_NEXOP\n")); + { +#line 176 + _jqp_op_negate(yy); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_PLACEHOLDER(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_PLACEHOLDER\n")); + { +#line 174 + __ = _jqp_placeholder(yy, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_NEXPRLEFT(yycontext *yy, char *yytext, int yyleng) +{ +#define r yy->__val[-1] +#define o yy->__val[-2] +#define l yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_NEXPRLEFT\n")); + { +#line 170 + __ = _jqp_expr(yy, l, o, r); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef r +#undef o +#undef l +} +YY_ACTION(void) yy_1_NEXPAIR(yycontext *yy, char *yytext, int yyleng) +{ +#define r yy->__val[-1] +#define o yy->__val[-2] +#define l yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_NEXPAIR\n")); + { +#line 166 + __ = _jqp_expr(yy, l, o, r); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef r +#undef o +#undef l +} +YY_ACTION(void) yy_2_NEXJOIN(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_NEXJOIN\n")); + { +#line 164 + __ = _jqp_unit_join(yy, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_NEXJOIN(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_NEXJOIN\n")); + { +#line 164 + _jqp_op_negate(yy); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_4_NEXPR(yycontext *yy, char *yytext, int yyleng) +{ +#define np yy->__val[-1] +#define j yy->__val[-2] +#define n yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_4_NEXPR\n")); + { +#line 162 + __ = _jqp_pop_expr_chain(yy, n); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef np +#undef j +#undef n +} +YY_ACTION(void) yy_3_NEXPR(yycontext *yy, char *yytext, int yyleng) +{ +#define np yy->__val[-1] +#define j yy->__val[-2] +#define n yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_NEXPR\n")); + { +#line 161 + _jqp_unit_push(yy, np); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef np +#undef j +#undef n +} +YY_ACTION(void) yy_2_NEXPR(yycontext *yy, char *yytext, int yyleng) +{ +#define np yy->__val[-1] +#define j yy->__val[-2] +#define n yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_NEXPR\n")); + { +#line 161 + _jqp_unit_push(yy, j); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef np +#undef j +#undef n +} +YY_ACTION(void) yy_1_NEXPR(yycontext *yy, char *yytext, int yyleng) +{ +#define np yy->__val[-1] +#define j yy->__val[-2] +#define n yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_NEXPR\n")); + { +#line 160 + _jqp_unit_push(yy, n); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef np +#undef j +#undef n +} +YY_ACTION(void) yy_1_NODE(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_NODE\n")); + { +#line 158 + __ = _jqp_node(yy, n); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +} +YY_ACTION(void) yy_1_FILTERANCHOR(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_FILTERANCHOR\n")); + { +#line 155 + __ = _jqp_string(yy, JQP_STR_ANCHOR, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_4_FILTER(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define fn yy->__val[-2] +#define a yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_4_FILTER\n")); + { +#line 153 + __ = _jqp_pop_node_chain(yy, fn); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +#undef fn +#undef a +} +YY_ACTION(void) yy_3_FILTER(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define fn yy->__val[-2] +#define a yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_FILTER\n")); + { +#line 153 + _jqp_unit_push(yy, n); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +#undef fn +#undef a +} +YY_ACTION(void) yy_2_FILTER(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define fn yy->__val[-2] +#define a yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_FILTER\n")); + { +#line 153 + _jqp_unit_push(yy, fn); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +#undef fn +#undef a +} +YY_ACTION(void) yy_1_FILTER(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define fn yy->__val[-2] +#define a yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_FILTER\n")); + { +#line 153 + _jqp_unit_push(yy, a); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +#undef fn +#undef a +} +YY_ACTION(void) yy_4_FILTEREXPR(yycontext *yy, char *yytext, int yyleng) +{ +#define f yy->__val[-1] +#define j yy->__val[-2] +#define ff yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_4_FILTEREXPR\n")); + { +#line 151 + __ = _jqp_pop_filter_factor_chain(yy, ff); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef f +#undef j +#undef ff +} +YY_ACTION(void) yy_3_FILTEREXPR(yycontext *yy, char *yytext, int yyleng) +{ +#define f yy->__val[-1] +#define j yy->__val[-2] +#define ff yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_FILTEREXPR\n")); + { +#line 151 + _jqp_unit_push(yy, f); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef f +#undef j +#undef ff +} +YY_ACTION(void) yy_2_FILTEREXPR(yycontext *yy, char *yytext, int yyleng) +{ +#define f yy->__val[-1] +#define j yy->__val[-2] +#define ff yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_FILTEREXPR\n")); + { +#line 151 + _jqp_unit_push(yy, j); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef f +#undef j +#undef ff +} +YY_ACTION(void) yy_1_FILTEREXPR(yycontext *yy, char *yytext, int yyleng) +{ +#define f yy->__val[-1] +#define j yy->__val[-2] +#define ff yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_FILTEREXPR\n")); + { +#line 150 + _jqp_unit_push(yy, ff); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef f +#undef j +#undef ff +} +YY_ACTION(void) yy_1_PSTRP(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_PSTRP\n")); + { +#line 140 + __ = _jqp_string(yy, 0, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_3_PROJFIELDS(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define sp yy->__val[-2] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_PROJFIELDS\n")); + { +#line 134 + __ = _jqp_pop_projfields_chain(yy, sp); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +#undef sp +} +YY_ACTION(void) yy_2_PROJFIELDS(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define sp yy->__val[-2] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_PROJFIELDS\n")); + { +#line 133 + _jqp_unit_push(yy, p); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +#undef sp +} +YY_ACTION(void) yy_1_PROJFIELDS(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define sp yy->__val[-2] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_PROJFIELDS\n")); + { +#line 133 + _jqp_unit_push(yy, sp); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +#undef sp +} +YY_ACTION(void) yy_1_PROJALL(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_PROJALL\n")); + { +#line 129 + __ = _jqp_string(yy, JQP_STR_PROJALIAS, "all"); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_4_PROJNODES(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define sn yy->__val[-2] +#define a yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_4_PROJNODES\n")); + { +#line 127 + __ = _jqp_pop_projection_nodes(yy, sn); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +#undef sn +#undef a +} +YY_ACTION(void) yy_3_PROJNODES(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define sn yy->__val[-2] +#define a yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_PROJNODES\n")); + { +#line 127 + _jqp_unit_push(yy, n);; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +#undef sn +#undef a +} +YY_ACTION(void) yy_2_PROJNODES(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define sn yy->__val[-2] +#define a yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_PROJNODES\n")); + { +#line 127 + _jqp_unit_push(yy, sn); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +#undef sn +#undef a +} +YY_ACTION(void) yy_1_PROJNODES(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define sn yy->__val[-2] +#define a yy->__val[-3] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_PROJNODES\n")); + { +#line 126 + __ = _jqp_projection(yy, a, 0); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +#undef sn +#undef a +} +YY_ACTION(void) yy_3_ORDERNODES(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define sn yy->__val[-2] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_ORDERNODES\n")); + { +#line 122 + __ = _jqp_pop_ordernodes(yy, sn) ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +#undef sn +} +YY_ACTION(void) yy_2_ORDERNODES(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define sn yy->__val[-2] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_ORDERNODES\n")); + { +#line 122 + _jqp_unit_push(yy, n); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +#undef sn +} +YY_ACTION(void) yy_1_ORDERNODES(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define sn yy->__val[-2] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_ORDERNODES\n")); + { +#line 122 + _jqp_unit_push(yy, sn); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +#undef sn +} +YY_ACTION(void) yy_2_ORDERBY(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_ORDERBY\n")); + { +#line 120 + p->string.flavour |= (yy->aux->negate ? JQP_STR_NEGATE : 0); _jqp_op_negate_reset(yy); _jqp_add_orderby(yy, p); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +} +YY_ACTION(void) yy_1_ORDERBY(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_ORDERBY\n")); + { +#line 118 + _jqp_op_negate(yy); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +} +YY_ACTION(void) yy_1_INVERSE(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_INVERSE\n")); + { +#line 116 + _jqp_set_inverse(yy); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_NOIDX(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_NOIDX\n")); + { +#line 114 + _jqp_set_noidx(yy); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_COUNT(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_COUNT\n")); + { +#line 112 + _jqp_set_aggregate_count(yy); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_3_LIMIT(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_LIMIT\n")); + { +#line 110 + _jqp_set_limit(yy, __); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +} +YY_ACTION(void) yy_2_LIMIT(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_LIMIT\n")); + { +#line 110 + __ = p; ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +} +YY_ACTION(void) yy_1_LIMIT(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_LIMIT\n")); + { +#line 110 + __ = _jqp_number(yy, JQP_INT_LIMIT, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +} +YY_ACTION(void) yy_3_SKIP(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_SKIP\n")); + { +#line 108 + _jqp_set_skip(yy, __); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +} +YY_ACTION(void) yy_2_SKIP(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_SKIP\n")); + { +#line 108 + __ = p; ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +} +YY_ACTION(void) yy_1_SKIP(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_SKIP\n")); + { +#line 108 + __ = _jqp_number(yy, JQP_INT_SKIP, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +} +YY_ACTION(void) yy_4_PROJECTION(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define sn yy->__val[-2] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_4_PROJECTION\n")); + { +#line 102 + __ = _jqp_pop_joined_projections(yy, sn); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +#undef sn +} +YY_ACTION(void) yy_3_PROJECTION(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define sn yy->__val[-2] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_PROJECTION\n")); + { +#line 101 + _jqp_push_joined_projection(yy, n); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +#undef sn +} +YY_ACTION(void) yy_2_PROJECTION(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define sn yy->__val[-2] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_PROJECTION\n")); + { +#line 101 + _jqp_string_push(yy, yytext, true); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +#undef sn +} +YY_ACTION(void) yy_1_PROJECTION(yycontext *yy, char *yytext, int yyleng) +{ +#define n yy->__val[-1] +#define sn yy->__val[-2] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_PROJECTION\n")); + { +#line 100 + _jqp_unit_push(yy, sn); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef n +#undef sn +} +YY_ACTION(void) yy_2_FILTERJOIN(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_FILTERJOIN\n")); + { +#line 94 + __ = _jqp_unit_join(yy, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_FILTERJOIN(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_FILTERJOIN\n")); + { +#line 94 + _jqp_op_negate(yy); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_2_FILTEREXPR_PK(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define a yy->__val[-2] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_FILTEREXPR_PK\n")); + { +#line 92 + __ = _jqp_create_filterexpr_pk(yy, p) ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +#undef a +} +YY_ACTION(void) yy_1_FILTEREXPR_PK(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define a yy->__val[-2] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_FILTEREXPR_PK\n")); + { +#line 90 + _jqp_unit_push(yy, a); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +#undef a +} +YY_ACTION(void) yy_6_QUERY(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define u yy->__val[-2] +#define a yy->__val[-3] +#define s yy->__val[-4] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_6_QUERY\n")); + { +#line 86 + _jqp_finish(yy); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +#undef u +#undef a +#undef s +} +YY_ACTION(void) yy_5_QUERY(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define u yy->__val[-2] +#define a yy->__val[-3] +#define s yy->__val[-4] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_5_QUERY\n")); + { +#line 84 + _jqp_set_projection(yy, p); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +#undef u +#undef a +#undef s +} +YY_ACTION(void) yy_4_QUERY(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define u yy->__val[-2] +#define a yy->__val[-3] +#define s yy->__val[-4] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_4_QUERY\n")); + { +#line 83 + _jqp_set_apply_upsert(yy, u); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +#undef u +#undef a +#undef s +} +YY_ACTION(void) yy_3_QUERY(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define u yy->__val[-2] +#define a yy->__val[-3] +#define s yy->__val[-4] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_QUERY\n")); + { +#line 83 + _jqp_set_apply_delete(yy); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +#undef u +#undef a +#undef s +} +YY_ACTION(void) yy_2_QUERY(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define u yy->__val[-2] +#define a yy->__val[-3] +#define s yy->__val[-4] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_QUERY\n")); + { +#line 83 + _jqp_set_apply(yy, a); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +#undef u +#undef a +#undef s +} +YY_ACTION(void) yy_1_QUERY(yycontext *yy, char *yytext, int yyleng) +{ +#define p yy->__val[-1] +#define u yy->__val[-2] +#define a yy->__val[-3] +#define s yy->__val[-4] +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_QUERY\n")); + { +#line 82 + _jqp_set_filters_expr(yy, s); ; + } +#undef yythunkpos +#undef yypos +#undef yy +#undef p +#undef u +#undef a +#undef s +} + +YY_RULE(int) yy_EOL(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "EOL")); + { int yypos2= yy->__pos, yythunkpos2= yy->__thunkpos; if (!yymatchString(yy, "\r\n")) goto l3; goto l2; + l3:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchChar(yy, '\n')) goto l4; goto l2; + l4:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchChar(yy, '\r')) goto l1; + } + l2:; + yyprintf((stderr, " ok %s @ %s\n", "EOL", yy->__buf+yy->__pos)); + return 1; + l1:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "EOL", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_SPACE(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "SPACE")); + { int yypos6= yy->__pos, yythunkpos6= yy->__thunkpos; if (!yymatchChar(yy, ' ')) goto l7; goto l6; + l7:; yy->__pos= yypos6; yy->__thunkpos= yythunkpos6; if (!yymatchChar(yy, '\t')) goto l8; goto l6; + l8:; yy->__pos= yypos6; yy->__thunkpos= yythunkpos6; if (!yy_EOL(yy)) goto l5; + } + l6:; + yyprintf((stderr, " ok %s @ %s\n", "SPACE", yy->__buf+yy->__pos)); + return 1; + l5:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "SPACE", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NUME(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "NUME")); if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\000\000\040\000\000\000\040\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l9; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\001\370\377\377\377\377\377\077\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l9; + l10:; + { int yypos11= yy->__pos, yythunkpos11= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\001\370\377\377\377\377\377\077\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l11; goto l10; + l11:; yy->__pos= yypos11; yy->__thunkpos= yythunkpos11; + } + yyprintf((stderr, " ok %s @ %s\n", "NUME", yy->__buf+yy->__pos)); + return 1; + l9:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NUME", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NUMF(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "NUMF")); if (!yymatchChar(yy, '.')) goto l12; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l12; + l13:; + { int yypos14= yy->__pos, yythunkpos14= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l14; goto l13; + l14:; yy->__pos= yypos14; yy->__thunkpos= yythunkpos14; + } + yyprintf((stderr, " ok %s @ %s\n", "NUMF", yy->__buf+yy->__pos)); + return 1; + l12:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NUMF", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NUMJ(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "NUMJ")); yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l15; +#undef yytext +#undef yyleng + } + { int yypos16= yy->__pos, yythunkpos16= yy->__thunkpos; if (!yymatchChar(yy, '-')) goto l16; goto l17; + l16:; yy->__pos= yypos16; yy->__thunkpos= yythunkpos16; + } + l17:; if (!yy_NUMI(yy)) goto l15; + { int yypos18= yy->__pos, yythunkpos18= yy->__thunkpos; if (!yy_NUMF(yy)) goto l18; goto l19; + l18:; yy->__pos= yypos18; yy->__thunkpos= yythunkpos18; + } + l19:; + { int yypos20= yy->__pos, yythunkpos20= yy->__thunkpos; if (!yy_NUME(yy)) goto l20; goto l21; + l20:; yy->__pos= yypos20; yy->__thunkpos= yythunkpos20; + } + l21:; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l15; +#undef yytext +#undef yyleng + } yyDo(yy, yy_1_NUMJ, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "NUMJ", yy->__buf+yy->__pos)); + return 1; + l15:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NUMJ", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_STRJ(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "STRJ")); if (!yymatchChar(yy, '"')) goto l22; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l22; +#undef yytext +#undef yyleng + } + l23:; + { int yypos24= yy->__pos, yythunkpos24= yy->__thunkpos; if (!yy_CHJ(yy)) goto l24; goto l23; + l24:; yy->__pos= yypos24; yy->__thunkpos= yythunkpos24; + } yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l22; +#undef yytext +#undef yyleng + } if (!yymatchChar(yy, '"')) goto l22; yyDo(yy, yy_1_STRJ, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "STRJ", yy->__buf+yy->__pos)); + return 1; + l22:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "STRJ", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_SARRJ(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "SARRJ")); if (!yymatchChar(yy, '[')) goto l25; yyDo(yy, yy_1_SARRJ, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "SARRJ", yy->__buf+yy->__pos)); + return 1; + l25:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "SARRJ", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_PAIRJ(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 2, 0); + yyprintf((stderr, "%s\n", "PAIRJ")); if (!yy_STRJ(yy)) goto l26; yyDo(yy, yySet, -2, 0); if (!yy__(yy)) goto l26; if (!yymatchChar(yy, ':')) goto l26; if (!yy__(yy)) goto l26; if (!yy_VALJ(yy)) goto l26; yyDo(yy, yySet, -1, 0); yyDo(yy, yy_1_PAIRJ, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "PAIRJ", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 2, 0); + return 1; + l26:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "PAIRJ", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_SOBJJ(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "SOBJJ")); if (!yymatchChar(yy, '{')) goto l27; yyDo(yy, yy_1_SOBJJ, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "SOBJJ", yy->__buf+yy->__pos)); + return 1; + l27:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "SOBJJ", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_CHJ(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "CHJ")); + { int yypos29= yy->__pos, yythunkpos29= yy->__thunkpos; if (!yymatchChar(yy, '\\')) goto l30; if (!yymatchChar(yy, '"')) goto l30; goto l29; + l30:; yy->__pos= yypos29; yy->__thunkpos= yythunkpos29; if (!yymatchChar(yy, '\\')) goto l31; if (!yymatchChar(yy, '\\')) goto l31; goto l29; + l31:; yy->__pos= yypos29; yy->__thunkpos= yythunkpos29; if (!yymatchChar(yy, '\\')) goto l32; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\000\000\000\000\000\000\104\100\024\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l32; goto l29; + l32:; yy->__pos= yypos29; yy->__thunkpos= yythunkpos29; if (!yymatchChar(yy, '\\')) goto l33; if (!yymatchChar(yy, 'u')) goto l33; if (!yy_HEX(yy)) goto l33; if (!yy_HEX(yy)) goto l33; if (!yy_HEX(yy)) goto l33; if (!yy_HEX(yy)) goto l33; goto l29; + l33:; yy->__pos= yypos29; yy->__thunkpos= yythunkpos29; + { int yypos34= yy->__pos, yythunkpos34= yy->__thunkpos; if (!yymatchChar(yy, '"')) goto l34; goto l28; + l34:; yy->__pos= yypos34; yy->__thunkpos= yythunkpos34; + } if (!yymatchDot(yy)) goto l28; + } + l29:; + yyprintf((stderr, " ok %s @ %s\n", "CHJ", yy->__buf+yy->__pos)); + return 1; + l28:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "CHJ", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_CHP(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "CHP")); + { int yypos36= yy->__pos, yythunkpos36= yy->__thunkpos; if (!yymatchChar(yy, '\\')) goto l37; if (!yymatchChar(yy, '\\')) goto l37; goto l36; + l37:; yy->__pos= yypos36; yy->__thunkpos= yythunkpos36; if (!yymatchChar(yy, '\\')) goto l38; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\000\000\000\000\000\000\104\100\024\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l38; goto l36; + l38:; yy->__pos= yypos36; yy->__thunkpos= yythunkpos36; if (!yymatchChar(yy, '\\')) goto l39; if (!yymatchChar(yy, 'u')) goto l39; if (!yy_HEX(yy)) goto l39; if (!yy_HEX(yy)) goto l39; if (!yy_HEX(yy)) goto l39; if (!yy_HEX(yy)) goto l39; goto l36; + l39:; yy->__pos= yypos36; yy->__thunkpos= yythunkpos36; + { int yypos40= yy->__pos, yythunkpos40= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\046\000\000\007\203\000\160\000\000\000\050\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l40; goto l35; + l40:; yy->__pos= yypos40; yy->__thunkpos= yythunkpos40; + } if (!yymatchDot(yy)) goto l35; + } + l36:; + yyprintf((stderr, " ok %s @ %s\n", "CHP", yy->__buf+yy->__pos)); + return 1; + l35:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "CHP", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_VALJ(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "VALJ")); + { int yypos42= yy->__pos, yythunkpos42= yy->__thunkpos; if (!yy_STRJ(yy)) goto l43; goto l42; + l43:; yy->__pos= yypos42; yy->__thunkpos= yythunkpos42; if (!yy_NUMJ(yy)) goto l44; goto l42; + l44:; yy->__pos= yypos42; yy->__thunkpos= yythunkpos42; if (!yy_OBJJ(yy)) goto l45; goto l42; + l45:; yy->__pos= yypos42; yy->__thunkpos= yythunkpos42; if (!yy_ARRJ(yy)) goto l46; goto l42; + l46:; yy->__pos= yypos42; yy->__thunkpos= yythunkpos42; if (!yymatchString(yy, "true")) goto l47; yyDo(yy, yy_1_VALJ, yy->__begin, yy->__end); goto l42; + l47:; yy->__pos= yypos42; yy->__thunkpos= yythunkpos42; if (!yymatchString(yy, "false")) goto l48; yyDo(yy, yy_2_VALJ, yy->__begin, yy->__end); goto l42; + l48:; yy->__pos= yypos42; yy->__thunkpos= yythunkpos42; if (!yymatchString(yy, "null")) goto l41; yyDo(yy, yy_3_VALJ, yy->__begin, yy->__end); + } + l42:; + yyprintf((stderr, " ok %s @ %s\n", "VALJ", yy->__buf+yy->__pos)); + return 1; + l41:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "VALJ", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NEXPRLEFT(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 3, 0); + yyprintf((stderr, "%s\n", "NEXPRLEFT")); if (!yymatchChar(yy, '[')) goto l49; if (!yy__(yy)) goto l49; if (!yy_STRSTAR(yy)) goto l49; yyDo(yy, yySet, -3, 0); if (!yy__(yy)) goto l49; if (!yy_NEXOP(yy)) goto l49; yyDo(yy, yySet, -2, 0); if (!yy__(yy)) goto l49; if (!yy_NEXRIGHT(yy)) goto l49; yyDo(yy, yySet, -1, 0); if (!yy__(yy)) goto l49; if (!yymatchChar(yy, ']')) goto l49; yyDo(yy, yy_1_NEXPRLEFT, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "NEXPRLEFT", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 3, 0); + return 1; + l49:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NEXPRLEFT", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_STRSTAR(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "STRSTAR")); if (!yymatchChar(yy, '*')) goto l50; yyDo(yy, yy_1_STRSTAR, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "STRSTAR", yy->__buf+yy->__pos)); + return 1; + l50:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "STRSTAR", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_DBLSTAR(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "DBLSTAR")); if (!yymatchString(yy, "**")) goto l51; yyDo(yy, yy_1_DBLSTAR, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "DBLSTAR", yy->__buf+yy->__pos)); + return 1; + l51:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "DBLSTAR", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NEXRIGHT(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "NEXRIGHT")); + { int yypos53= yy->__pos, yythunkpos53= yy->__thunkpos; if (!yy_PLACEHOLDER(yy)) goto l54; goto l53; + l54:; yy->__pos= yypos53; yy->__thunkpos= yythunkpos53; if (!yy_VALJ(yy)) goto l55; goto l53; + l55:; yy->__pos= yypos53; yy->__thunkpos= yythunkpos53; if (!yy_STRP(yy)) goto l52; + } + l53:; + yyprintf((stderr, " ok %s @ %s\n", "NEXRIGHT", yy->__buf+yy->__pos)); + return 1; + l52:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NEXRIGHT", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NEXOP(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "NEXOP")); + { int yypos57= yy->__pos, yythunkpos57= yy->__thunkpos; + { int yypos59= yy->__pos, yythunkpos59= yy->__thunkpos; if (!yymatchString(yy, "not")) goto l59; if (!yy___(yy)) goto l59; yyDo(yy, yy_1_NEXOP, yy->__begin, yy->__end); goto l60; + l59:; yy->__pos= yypos59; yy->__thunkpos= yythunkpos59; + } + l60:; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l58; +#undef yytext +#undef yyleng + } + { int yypos61= yy->__pos, yythunkpos61= yy->__thunkpos; if (!yymatchString(yy, "in")) goto l62; goto l61; + l62:; yy->__pos= yypos61; yy->__thunkpos= yythunkpos61; if (!yymatchString(yy, "ni")) goto l63; goto l61; + l63:; yy->__pos= yypos61; yy->__thunkpos= yythunkpos61; if (!yymatchString(yy, "re")) goto l58; + } + l61:; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l58; +#undef yytext +#undef yyleng + } yyDo(yy, yy_2_NEXOP, yy->__begin, yy->__end); goto l57; + l58:; yy->__pos= yypos57; yy->__thunkpos= yythunkpos57; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l64; +#undef yytext +#undef yyleng + } + { int yypos65= yy->__pos, yythunkpos65= yy->__thunkpos; if (!yymatchString(yy, ">=")) goto l66; goto l65; + l66:; yy->__pos= yypos65; yy->__thunkpos= yythunkpos65; if (!yymatchString(yy, "gte")) goto l64; + } + l65:; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l64; +#undef yytext +#undef yyleng + } yyDo(yy, yy_3_NEXOP, yy->__begin, yy->__end); goto l57; + l64:; yy->__pos= yypos57; yy->__thunkpos= yythunkpos57; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l67; +#undef yytext +#undef yyleng + } + { int yypos68= yy->__pos, yythunkpos68= yy->__thunkpos; if (!yymatchString(yy, "<=")) goto l69; goto l68; + l69:; yy->__pos= yypos68; yy->__thunkpos= yythunkpos68; if (!yymatchString(yy, "lte")) goto l67; + } + l68:; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l67; +#undef yytext +#undef yyleng + } yyDo(yy, yy_4_NEXOP, yy->__begin, yy->__end); goto l57; + l67:; yy->__pos= yypos57; yy->__thunkpos= yythunkpos57; + { int yypos71= yy->__pos, yythunkpos71= yy->__thunkpos; if (!yymatchChar(yy, '!')) goto l71; if (!yy__(yy)) goto l71; yyDo(yy, yy_5_NEXOP, yy->__begin, yy->__end); goto l72; + l71:; yy->__pos= yypos71; yy->__thunkpos= yythunkpos71; + } + l72:; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l70; +#undef yytext +#undef yyleng + } + { int yypos73= yy->__pos, yythunkpos73= yy->__thunkpos; if (!yymatchChar(yy, '=')) goto l74; goto l73; + l74:; yy->__pos= yypos73; yy->__thunkpos= yythunkpos73; if (!yymatchString(yy, "eq")) goto l75; goto l73; + l75:; yy->__pos= yypos73; yy->__thunkpos= yythunkpos73; if (!yymatchChar(yy, '~')) goto l70; + } + l73:; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l70; +#undef yytext +#undef yyleng + } yyDo(yy, yy_6_NEXOP, yy->__begin, yy->__end); goto l57; + l70:; yy->__pos= yypos57; yy->__thunkpos= yythunkpos57; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l76; +#undef yytext +#undef yyleng + } + { int yypos77= yy->__pos, yythunkpos77= yy->__thunkpos; if (!yymatchChar(yy, '>')) goto l78; goto l77; + l78:; yy->__pos= yypos77; yy->__thunkpos= yythunkpos77; if (!yymatchString(yy, "gt")) goto l76; + } + l77:; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l76; +#undef yytext +#undef yyleng + } yyDo(yy, yy_7_NEXOP, yy->__begin, yy->__end); goto l57; + l76:; yy->__pos= yypos57; yy->__thunkpos= yythunkpos57; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l79; +#undef yytext +#undef yyleng + } + { int yypos80= yy->__pos, yythunkpos80= yy->__thunkpos; if (!yymatchChar(yy, '<')) goto l81; goto l80; + l81:; yy->__pos= yypos80; yy->__thunkpos= yythunkpos80; if (!yymatchString(yy, "lt")) goto l79; + } + l80:; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l79; +#undef yytext +#undef yyleng + } yyDo(yy, yy_8_NEXOP, yy->__begin, yy->__end); goto l57; + l79:; yy->__pos= yypos57; yy->__thunkpos= yythunkpos57; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l56; +#undef yytext +#undef yyleng + } if (!yymatchChar(yy, '~')) goto l56; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l56; +#undef yytext +#undef yyleng + } yyDo(yy, yy_9_NEXOP, yy->__begin, yy->__end); + } + l57:; + yyprintf((stderr, " ok %s @ %s\n", "NEXOP", yy->__buf+yy->__pos)); + return 1; + l56:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NEXOP", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NEXLEFT(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "NEXLEFT")); + { int yypos83= yy->__pos, yythunkpos83= yy->__thunkpos; if (!yy_DBLSTAR(yy)) goto l84; goto l83; + l84:; yy->__pos= yypos83; yy->__thunkpos= yythunkpos83; if (!yy_STRSTAR(yy)) goto l85; goto l83; + l85:; yy->__pos= yypos83; yy->__thunkpos= yythunkpos83; if (!yy_STRN(yy)) goto l86; goto l83; + l86:; yy->__pos= yypos83; yy->__thunkpos= yythunkpos83; if (!yy_NEXPRLEFT(yy)) goto l87; goto l83; + l87:; yy->__pos= yypos83; yy->__thunkpos= yythunkpos83; if (!yy_STRP(yy)) goto l82; + } + l83:; + yyprintf((stderr, " ok %s @ %s\n", "NEXLEFT", yy->__buf+yy->__pos)); + return 1; + l82:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NEXLEFT", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NEXJOIN(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "NEXJOIN")); yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l88; +#undef yytext +#undef yyleng + } + { int yypos89= yy->__pos, yythunkpos89= yy->__thunkpos; if (!yymatchString(yy, "and")) goto l90; goto l89; + l90:; yy->__pos= yypos89; yy->__thunkpos= yythunkpos89; if (!yymatchString(yy, "or")) goto l88; + } + l89:; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l88; +#undef yytext +#undef yyleng + } + { int yypos91= yy->__pos, yythunkpos91= yy->__thunkpos; if (!yy___(yy)) goto l91; if (!yymatchString(yy, "not")) goto l91; yyDo(yy, yy_1_NEXJOIN, yy->__begin, yy->__end); goto l92; + l91:; yy->__pos= yypos91; yy->__thunkpos= yythunkpos91; + } + l92:; yyDo(yy, yy_2_NEXJOIN, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "NEXJOIN", yy->__buf+yy->__pos)); + return 1; + l88:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NEXJOIN", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NEXPAIR(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 3, 0); + yyprintf((stderr, "%s\n", "NEXPAIR")); if (!yy_NEXLEFT(yy)) goto l93; yyDo(yy, yySet, -3, 0); if (!yy__(yy)) goto l93; if (!yy_NEXOP(yy)) goto l93; yyDo(yy, yySet, -2, 0); if (!yy__(yy)) goto l93; if (!yy_NEXRIGHT(yy)) goto l93; yyDo(yy, yySet, -1, 0); yyDo(yy, yy_1_NEXPAIR, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "NEXPAIR", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 3, 0); + return 1; + l93:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NEXPAIR", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_STRP(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "STRP")); yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l94; +#undef yytext +#undef yyleng + } if (!yy_CHP(yy)) goto l94; + l95:; + { int yypos96= yy->__pos, yythunkpos96= yy->__thunkpos; if (!yy_CHP(yy)) goto l96; goto l95; + l96:; yy->__pos= yypos96; yy->__thunkpos= yythunkpos96; + } yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l94; +#undef yytext +#undef yyleng + } yyDo(yy, yy_1_STRP, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "STRP", yy->__buf+yy->__pos)); + return 1; + l94:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "STRP", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NEXPR(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 3, 0); + yyprintf((stderr, "%s\n", "NEXPR")); if (!yymatchChar(yy, '[')) goto l97; if (!yy__(yy)) goto l97; if (!yy_NEXPAIR(yy)) goto l97; yyDo(yy, yySet, -3, 0); yyDo(yy, yy_1_NEXPR, yy->__begin, yy->__end); + l98:; + { int yypos99= yy->__pos, yythunkpos99= yy->__thunkpos; if (!yy___(yy)) goto l99; if (!yy_NEXJOIN(yy)) goto l99; yyDo(yy, yySet, -2, 0); yyDo(yy, yy_2_NEXPR, yy->__begin, yy->__end); if (!yy___(yy)) goto l99; if (!yy_NEXPAIR(yy)) goto l99; yyDo(yy, yySet, -1, 0); yyDo(yy, yy_3_NEXPR, yy->__begin, yy->__end); goto l98; + l99:; yy->__pos= yypos99; yy->__thunkpos= yythunkpos99; + } if (!yy__(yy)) goto l97; if (!yymatchChar(yy, ']')) goto l97; yyDo(yy, yy_4_NEXPR, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "NEXPR", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 3, 0); + return 1; + l97:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NEXPR", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NODE(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 1, 0); + yyprintf((stderr, "%s\n", "NODE")); if (!yymatchChar(yy, '/')) goto l100; + { int yypos101= yy->__pos, yythunkpos101= yy->__thunkpos; if (!yy_STRN(yy)) goto l102; yyDo(yy, yySet, -1, 0); goto l101; + l102:; yy->__pos= yypos101; yy->__thunkpos= yythunkpos101; if (!yy_NEXPR(yy)) goto l103; yyDo(yy, yySet, -1, 0); goto l101; + l103:; yy->__pos= yypos101; yy->__thunkpos= yythunkpos101; if (!yy_STRP(yy)) goto l100; yyDo(yy, yySet, -1, 0); + } + l101:; yyDo(yy, yy_1_NODE, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "NODE", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 1, 0); + return 1; + l100:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NODE", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_FILTER(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 3, 0); + yyprintf((stderr, "%s\n", "FILTER")); + { int yypos105= yy->__pos, yythunkpos105= yy->__thunkpos; if (!yy_FILTERANCHOR(yy)) goto l105; yyDo(yy, yySet, -3, 0); yyDo(yy, yy_1_FILTER, yy->__begin, yy->__end); goto l106; + l105:; yy->__pos= yypos105; yy->__thunkpos= yythunkpos105; + } + l106:; if (!yy_NODE(yy)) goto l104; yyDo(yy, yySet, -2, 0); yyDo(yy, yy_2_FILTER, yy->__begin, yy->__end); + l107:; + { int yypos108= yy->__pos, yythunkpos108= yy->__thunkpos; if (!yy_NODE(yy)) goto l108; yyDo(yy, yySet, -1, 0); yyDo(yy, yy_3_FILTER, yy->__begin, yy->__end); goto l107; + l108:; yy->__pos= yypos108; yy->__thunkpos= yythunkpos108; + } yyDo(yy, yy_4_FILTER, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "FILTER", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 3, 0); + return 1; + l104:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "FILTER", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_FILTERFACTOR(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "FILTERFACTOR")); + { int yypos110= yy->__pos, yythunkpos110= yy->__thunkpos; if (!yy_FILTER(yy)) goto l111; goto l110; + l111:; yy->__pos= yypos110; yy->__thunkpos= yythunkpos110; if (!yymatchChar(yy, '(')) goto l109; if (!yy_FILTEREXPR(yy)) goto l109; if (!yymatchChar(yy, ')')) goto l109; + } + l110:; + yyprintf((stderr, " ok %s @ %s\n", "FILTERFACTOR", yy->__buf+yy->__pos)); + return 1; + l109:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "FILTERFACTOR", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_HEX(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "HEX")); if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\003\176\000\000\000\176\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l112; + yyprintf((stderr, " ok %s @ %s\n", "HEX", yy->__buf+yy->__pos)); + return 1; + l112:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "HEX", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_PCHP(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "PCHP")); + { int yypos114= yy->__pos, yythunkpos114= yy->__thunkpos; if (!yymatchChar(yy, '\\')) goto l115; if (!yymatchChar(yy, '\\')) goto l115; goto l114; + l115:; yy->__pos= yypos114; yy->__thunkpos= yythunkpos114; if (!yymatchChar(yy, '\\')) goto l116; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\000\000\000\000\000\000\104\100\024\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l116; goto l114; + l116:; yy->__pos= yypos114; yy->__thunkpos= yythunkpos114; if (!yymatchChar(yy, '\\')) goto l117; if (!yymatchChar(yy, 'u')) goto l117; if (!yy_HEX(yy)) goto l117; if (!yy_HEX(yy)) goto l117; if (!yy_HEX(yy)) goto l117; if (!yy_HEX(yy)) goto l117; goto l114; + l117:; yy->__pos= yypos114; yy->__thunkpos= yythunkpos114; + { int yypos118= yy->__pos, yythunkpos118= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\046\000\000\005\220\000\000\000\000\000\000\000\000\000\050\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l118; goto l113; + l118:; yy->__pos= yypos118; yy->__thunkpos= yythunkpos118; + } if (!yymatchDot(yy)) goto l113; + } + l114:; + yyprintf((stderr, " ok %s @ %s\n", "PCHP", yy->__buf+yy->__pos)); + return 1; + l113:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "PCHP", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_PSTRP(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "PSTRP")); yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l119; +#undef yytext +#undef yyleng + } if (!yy_PCHP(yy)) goto l119; + l120:; + { int yypos121= yy->__pos, yythunkpos121= yy->__thunkpos; if (!yy_PCHP(yy)) goto l121; goto l120; + l121:; yy->__pos= yypos121; yy->__thunkpos= yythunkpos121; + } yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l119; +#undef yytext +#undef yyleng + } yyDo(yy, yy_1_PSTRP, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "PSTRP", yy->__buf+yy->__pos)); + return 1; + l119:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "PSTRP", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_STRN(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "STRN")); if (!yymatchChar(yy, '"')) goto l122; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l122; +#undef yytext +#undef yyleng + } if (!yy_CHJ(yy)) goto l122; + l123:; + { int yypos124= yy->__pos, yythunkpos124= yy->__thunkpos; if (!yy_CHJ(yy)) goto l124; goto l123; + l124:; yy->__pos= yypos124; yy->__thunkpos= yythunkpos124; + } yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l122; +#undef yytext +#undef yyleng + } if (!yymatchChar(yy, '"')) goto l122; yyDo(yy, yy_1_STRN, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "STRN", yy->__buf+yy->__pos)); + return 1; + l122:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "STRN", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_PROJFIELDS(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 2, 0); + yyprintf((stderr, "%s\n", "PROJFIELDS")); if (!yymatchChar(yy, '{')) goto l125; if (!yy__(yy)) goto l125; if (!yy_PROJPROP(yy)) goto l125; yyDo(yy, yySet, -2, 0); yyDo(yy, yy_1_PROJFIELDS, yy->__begin, yy->__end); + l126:; + { int yypos127= yy->__pos, yythunkpos127= yy->__thunkpos; if (!yy__(yy)) goto l127; if (!yymatchChar(yy, ',')) goto l127; if (!yy__(yy)) goto l127; if (!yy_PROJPROP(yy)) goto l127; yyDo(yy, yySet, -1, 0); yyDo(yy, yy_2_PROJFIELDS, yy->__begin, yy->__end); goto l126; + l127:; yy->__pos= yypos127; yy->__thunkpos= yythunkpos127; + } if (!yy__(yy)) goto l125; if (!yymatchChar(yy, '}')) goto l125; yyDo(yy, yy_3_PROJFIELDS, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "PROJFIELDS", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 2, 0); + return 1; + l125:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "PROJFIELDS", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_PROJNODE(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "PROJNODE")); if (!yymatchChar(yy, '/')) goto l128; + { int yypos129= yy->__pos, yythunkpos129= yy->__thunkpos; if (!yy_PROJFIELDS(yy)) goto l130; goto l129; + l130:; yy->__pos= yypos129; yy->__thunkpos= yythunkpos129; if (!yy_PROJPROP(yy)) goto l128; + } + l129:; + yyprintf((stderr, " ok %s @ %s\n", "PROJNODE", yy->__buf+yy->__pos)); + return 1; + l128:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "PROJNODE", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_PROJALL(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "PROJALL")); if (!yymatchString(yy, "all")) goto l131; yyDo(yy, yy_1_PROJALL, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "PROJALL", yy->__buf+yy->__pos)); + return 1; + l131:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "PROJALL", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_PROJPROP(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "PROJPROP")); + { int yypos133= yy->__pos, yythunkpos133= yy->__thunkpos; if (!yy_STRN(yy)) goto l134; goto l133; + l134:; yy->__pos= yypos133; yy->__thunkpos= yythunkpos133; if (!yy_PSTRP(yy)) goto l132; + } + l133:; + yyprintf((stderr, " ok %s @ %s\n", "PROJPROP", yy->__buf+yy->__pos)); + return 1; + l132:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "PROJPROP", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_ORDERNODE(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "ORDERNODE")); if (!yymatchChar(yy, '/')) goto l135; if (!yy_PROJPROP(yy)) goto l135; + yyprintf((stderr, " ok %s @ %s\n", "ORDERNODE", yy->__buf+yy->__pos)); + return 1; + l135:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "ORDERNODE", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_ORDERNODES(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 2, 0); + yyprintf((stderr, "%s\n", "ORDERNODES")); if (!yy_ORDERNODE(yy)) goto l136; yyDo(yy, yySet, -2, 0); yyDo(yy, yy_1_ORDERNODES, yy->__begin, yy->__end); + l137:; + { int yypos138= yy->__pos, yythunkpos138= yy->__thunkpos; if (!yy_ORDERNODE(yy)) goto l138; yyDo(yy, yySet, -1, 0); yyDo(yy, yy_2_ORDERNODES, yy->__begin, yy->__end); goto l137; + l138:; yy->__pos= yypos138; yy->__thunkpos= yythunkpos138; + } yyDo(yy, yy_3_ORDERNODES, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "ORDERNODES", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 2, 0); + return 1; + l136:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "ORDERNODES", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NUMI(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "NUMI")); + { int yypos140= yy->__pos, yythunkpos140= yy->__thunkpos; if (!yymatchChar(yy, '0')) goto l141; goto l140; + l141:; yy->__pos= yypos140; yy->__thunkpos= yythunkpos140; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\376\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l139; + l142:; + { int yypos143= yy->__pos, yythunkpos143= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l143; goto l142; + l143:; yy->__pos= yypos143; yy->__thunkpos= yythunkpos143; + } + } + l140:; + yyprintf((stderr, " ok %s @ %s\n", "NUMI", yy->__buf+yy->__pos)); + return 1; + l139:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NUMI", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_INVERSE(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "INVERSE")); if (!yymatchString(yy, "inverse")) goto l144; yyDo(yy, yy_1_INVERSE, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "INVERSE", yy->__buf+yy->__pos)); + return 1; + l144:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "INVERSE", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NOIDX(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "NOIDX")); if (!yymatchString(yy, "noidx")) goto l145; yyDo(yy, yy_1_NOIDX, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "NOIDX", yy->__buf+yy->__pos)); + return 1; + l145:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NOIDX", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_COUNT(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "COUNT")); if (!yymatchString(yy, "count")) goto l146; yyDo(yy, yy_1_COUNT, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "COUNT", yy->__buf+yy->__pos)); + return 1; + l146:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "COUNT", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_ORDERBY(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 1, 0); + yyprintf((stderr, "%s\n", "ORDERBY")); + { int yypos148= yy->__pos, yythunkpos148= yy->__thunkpos; if (!yymatchString(yy, "asc")) goto l149; goto l148; + l149:; yy->__pos= yypos148; yy->__thunkpos= yythunkpos148; if (!yymatchString(yy, "desc")) goto l147; yyDo(yy, yy_1_ORDERBY, yy->__begin, yy->__end); + } + l148:; if (!yy___(yy)) goto l147; + { int yypos150= yy->__pos, yythunkpos150= yy->__thunkpos; if (!yy_ORDERNODES(yy)) goto l151; yyDo(yy, yySet, -1, 0); goto l150; + l151:; yy->__pos= yypos150; yy->__thunkpos= yythunkpos150; if (!yy_PLACEHOLDER(yy)) goto l147; yyDo(yy, yySet, -1, 0); + } + l150:; yyDo(yy, yy_2_ORDERBY, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "ORDERBY", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 1, 0); + return 1; + l147:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "ORDERBY", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_LIMIT(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 1, 0); + yyprintf((stderr, "%s\n", "LIMIT")); if (!yymatchString(yy, "limit")) goto l152; if (!yy___(yy)) goto l152; + { int yypos153= yy->__pos, yythunkpos153= yy->__thunkpos; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l154; +#undef yytext +#undef yyleng + } if (!yy_NUMI(yy)) goto l154; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l154; +#undef yytext +#undef yyleng + } yyDo(yy, yy_1_LIMIT, yy->__begin, yy->__end); goto l153; + l154:; yy->__pos= yypos153; yy->__thunkpos= yythunkpos153; if (!yy_PLACEHOLDER(yy)) goto l152; yyDo(yy, yySet, -1, 0); yyDo(yy, yy_2_LIMIT, yy->__begin, yy->__end); + } + l153:; yyDo(yy, yy_3_LIMIT, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "LIMIT", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 1, 0); + return 1; + l152:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "LIMIT", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_SKIP(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 1, 0); + yyprintf((stderr, "%s\n", "SKIP")); if (!yymatchString(yy, "skip")) goto l155; if (!yy___(yy)) goto l155; + { int yypos156= yy->__pos, yythunkpos156= yy->__thunkpos; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l157; +#undef yytext +#undef yyleng + } if (!yy_NUMI(yy)) goto l157; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l157; +#undef yytext +#undef yyleng + } yyDo(yy, yy_1_SKIP, yy->__begin, yy->__end); goto l156; + l157:; yy->__pos= yypos156; yy->__thunkpos= yythunkpos156; if (!yy_PLACEHOLDER(yy)) goto l155; yyDo(yy, yySet, -1, 0); yyDo(yy, yy_2_SKIP, yy->__begin, yy->__end); + } + l156:; yyDo(yy, yy_3_SKIP, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "SKIP", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 1, 0); + return 1; + l155:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "SKIP", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_OPT(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "OPT")); + { int yypos159= yy->__pos, yythunkpos159= yy->__thunkpos; if (!yy_SKIP(yy)) goto l160; goto l159; + l160:; yy->__pos= yypos159; yy->__thunkpos= yythunkpos159; if (!yy_LIMIT(yy)) goto l161; goto l159; + l161:; yy->__pos= yypos159; yy->__thunkpos= yythunkpos159; if (!yy_ORDERBY(yy)) goto l162; goto l159; + l162:; yy->__pos= yypos159; yy->__thunkpos= yythunkpos159; if (!yy_COUNT(yy)) goto l163; goto l159; + l163:; yy->__pos= yypos159; yy->__thunkpos= yythunkpos159; if (!yy_NOIDX(yy)) goto l164; goto l159; + l164:; yy->__pos= yypos159; yy->__thunkpos= yythunkpos159; if (!yy_INVERSE(yy)) goto l158; + } + l159:; + yyprintf((stderr, " ok %s @ %s\n", "OPT", yy->__buf+yy->__pos)); + return 1; + l158:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "OPT", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_PROJOIN(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "PROJOIN")); + { int yypos166= yy->__pos, yythunkpos166= yy->__thunkpos; if (!yymatchChar(yy, '+')) goto l167; goto l166; + l167:; yy->__pos= yypos166; yy->__thunkpos= yythunkpos166; if (!yymatchChar(yy, '-')) goto l165; + } + l166:; + yyprintf((stderr, " ok %s @ %s\n", "PROJOIN", yy->__buf+yy->__pos)); + return 1; + l165:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "PROJOIN", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_PROJNODES(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 3, 0); + yyprintf((stderr, "%s\n", "PROJNODES")); + { int yypos169= yy->__pos, yythunkpos169= yy->__thunkpos; if (!yy_PROJALL(yy)) goto l170; yyDo(yy, yySet, -3, 0); yyDo(yy, yy_1_PROJNODES, yy->__begin, yy->__end); goto l169; + l170:; yy->__pos= yypos169; yy->__thunkpos= yythunkpos169; if (!yy_PROJNODE(yy)) goto l168; yyDo(yy, yySet, -2, 0); yyDo(yy, yy_2_PROJNODES, yy->__begin, yy->__end); + l171:; + { int yypos172= yy->__pos, yythunkpos172= yy->__thunkpos; if (!yy_PROJNODE(yy)) goto l172; yyDo(yy, yySet, -1, 0); yyDo(yy, yy_3_PROJNODES, yy->__begin, yy->__end); goto l171; + l172:; yy->__pos= yypos172; yy->__thunkpos= yythunkpos172; + } yyDo(yy, yy_4_PROJNODES, yy->__begin, yy->__end); + } + l169:; + yyprintf((stderr, " ok %s @ %s\n", "PROJNODES", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 3, 0); + return 1; + l168:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "PROJNODES", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_ARRJ(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 3, 0); + yyprintf((stderr, "%s\n", "ARRJ")); if (!yy_SARRJ(yy)) goto l173; yyDo(yy, yySet, -3, 0); yyDo(yy, yy_1_ARRJ, yy->__begin, yy->__end); if (!yy__(yy)) goto l173; + { int yypos174= yy->__pos, yythunkpos174= yy->__thunkpos; if (!yy_VALJ(yy)) goto l174; yyDo(yy, yySet, -2, 0); yyDo(yy, yy_2_ARRJ, yy->__begin, yy->__end); + l176:; + { int yypos177= yy->__pos, yythunkpos177= yy->__thunkpos; if (!yy__(yy)) goto l177; if (!yymatchChar(yy, ',')) goto l177; if (!yy__(yy)) goto l177; if (!yy_VALJ(yy)) goto l177; yyDo(yy, yySet, -1, 0); yyDo(yy, yy_3_ARRJ, yy->__begin, yy->__end); goto l176; + l177:; yy->__pos= yypos177; yy->__thunkpos= yythunkpos177; + } goto l175; + l174:; yy->__pos= yypos174; yy->__thunkpos= yythunkpos174; + } + l175:; if (!yy__(yy)) goto l173; if (!yymatchChar(yy, ']')) goto l173; yyDo(yy, yy_4_ARRJ, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "ARRJ", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 3, 0); + return 1; + l173:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "ARRJ", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_OBJJ(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 3, 0); + yyprintf((stderr, "%s\n", "OBJJ")); if (!yy_SOBJJ(yy)) goto l178; yyDo(yy, yySet, -3, 0); yyDo(yy, yy_1_OBJJ, yy->__begin, yy->__end); if (!yy__(yy)) goto l178; + { int yypos179= yy->__pos, yythunkpos179= yy->__thunkpos; if (!yy_PAIRJ(yy)) goto l179; yyDo(yy, yySet, -2, 0); yyDo(yy, yy_2_OBJJ, yy->__begin, yy->__end); + l181:; + { int yypos182= yy->__pos, yythunkpos182= yy->__thunkpos; if (!yy__(yy)) goto l182; if (!yymatchChar(yy, ',')) goto l182; if (!yy__(yy)) goto l182; if (!yy_PAIRJ(yy)) goto l182; yyDo(yy, yySet, -1, 0); yyDo(yy, yy_3_OBJJ, yy->__begin, yy->__end); goto l181; + l182:; yy->__pos= yypos182; yy->__thunkpos= yythunkpos182; + } goto l180; + l179:; yy->__pos= yypos179; yy->__thunkpos= yythunkpos179; + } + l180:; if (!yy__(yy)) goto l178; if (!yymatchChar(yy, '}')) goto l178; yyDo(yy, yy_4_OBJJ, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "OBJJ", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 3, 0); + return 1; + l178:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "OBJJ", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy___(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "__")); if (!yy_SPACE(yy)) goto l183; + l184:; + { int yypos185= yy->__pos, yythunkpos185= yy->__thunkpos; if (!yy_SPACE(yy)) goto l185; goto l184; + l185:; yy->__pos= yypos185; yy->__thunkpos= yythunkpos185; + } + yyprintf((stderr, " ok %s @ %s\n", "__", yy->__buf+yy->__pos)); + return 1; + l183:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "__", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_FILTERJOIN(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "FILTERJOIN")); yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l186; +#undef yytext +#undef yyleng + } + { int yypos187= yy->__pos, yythunkpos187= yy->__thunkpos; if (!yymatchString(yy, "and")) goto l188; goto l187; + l188:; yy->__pos= yypos187; yy->__thunkpos= yythunkpos187; if (!yymatchString(yy, "or")) goto l186; + } + l187:; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l186; +#undef yytext +#undef yyleng + } + { int yypos189= yy->__pos, yythunkpos189= yy->__thunkpos; if (!yy___(yy)) goto l189; if (!yymatchString(yy, "not")) goto l189; yyDo(yy, yy_1_FILTERJOIN, yy->__begin, yy->__end); goto l190; + l189:; yy->__pos= yypos189; yy->__thunkpos= yythunkpos189; + } + l190:; yyDo(yy, yy_2_FILTERJOIN, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "FILTERJOIN", yy->__buf+yy->__pos)); + return 1; + l186:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "FILTERJOIN", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NUMPK_ARR(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 3, 0); + yyprintf((stderr, "%s\n", "NUMPK_ARR")); if (!yy_SARRJ(yy)) goto l191; yyDo(yy, yySet, -3, 0); yyDo(yy, yy_1_NUMPK_ARR, yy->__begin, yy->__end); if (!yy__(yy)) goto l191; + { int yypos192= yy->__pos, yythunkpos192= yy->__thunkpos; if (!yy_NUMPK(yy)) goto l192; yyDo(yy, yySet, -2, 0); yyDo(yy, yy_2_NUMPK_ARR, yy->__begin, yy->__end); + l194:; + { int yypos195= yy->__pos, yythunkpos195= yy->__thunkpos; if (!yy__(yy)) goto l195; if (!yymatchChar(yy, ',')) goto l195; if (!yy__(yy)) goto l195; if (!yy_NUMPK(yy)) goto l195; yyDo(yy, yySet, -1, 0); yyDo(yy, yy_3_NUMPK_ARR, yy->__begin, yy->__end); goto l194; + l195:; yy->__pos= yypos195; yy->__thunkpos= yythunkpos195; + } goto l193; + l192:; yy->__pos= yypos192; yy->__thunkpos= yythunkpos192; + } + l193:; if (!yy__(yy)) goto l191; if (!yymatchChar(yy, ']')) goto l191; yyDo(yy, yy_4_NUMPK_ARR, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "NUMPK_ARR", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 3, 0); + return 1; + l191:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NUMPK_ARR", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NUMPK(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "NUMPK")); yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l196; +#undef yytext +#undef yyleng + } if (!yy_NUMI(yy)) goto l196; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l196; +#undef yytext +#undef yyleng + } yyDo(yy, yy_1_NUMPK, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "NUMPK", yy->__buf+yy->__pos)); + return 1; + l196:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NUMPK", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_PLACEHOLDER(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "PLACEHOLDER")); if (!yymatchChar(yy, ':')) goto l197; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l197; +#undef yytext +#undef yyleng + } + { int yypos198= yy->__pos, yythunkpos198= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\003\376\377\377\007\376\377\377\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l199; + l200:; + { int yypos201= yy->__pos, yythunkpos201= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\003\376\377\377\007\376\377\377\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l201; goto l200; + l201:; yy->__pos= yypos201; yy->__thunkpos= yythunkpos201; + } goto l198; + l199:; yy->__pos= yypos198; yy->__thunkpos= yythunkpos198; if (!yymatchChar(yy, '?')) goto l197; + } + l198:; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l197; +#undef yytext +#undef yyleng + } yyDo(yy, yy_1_PLACEHOLDER, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "PLACEHOLDER", yy->__buf+yy->__pos)); + return 1; + l197:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "PLACEHOLDER", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_FILTERANCHOR(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "FILTERANCHOR")); if (!yymatchChar(yy, '@')) goto l202; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l202; +#undef yytext +#undef yyleng + } if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\040\377\003\376\377\377\207\376\377\377\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l202; + l203:; + { int yypos204= yy->__pos, yythunkpos204= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\040\377\003\376\377\377\207\376\377\377\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l204; goto l203; + l204:; yy->__pos= yypos204; yy->__thunkpos= yythunkpos204; + } yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l202; +#undef yytext +#undef yyleng + } yyDo(yy, yy_1_FILTERANCHOR, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "FILTERANCHOR", yy->__buf+yy->__pos)); + return 1; + l202:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "FILTERANCHOR", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_FILTEREXPR(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 3, 0); + yyprintf((stderr, "%s\n", "FILTEREXPR")); if (!yy_FILTERFACTOR(yy)) goto l205; yyDo(yy, yySet, -3, 0); yyDo(yy, yy_1_FILTEREXPR, yy->__begin, yy->__end); + l206:; + { int yypos207= yy->__pos, yythunkpos207= yy->__thunkpos; if (!yy___(yy)) goto l207; if (!yy_FILTERJOIN(yy)) goto l207; yyDo(yy, yySet, -2, 0); yyDo(yy, yy_2_FILTEREXPR, yy->__begin, yy->__end); if (!yy___(yy)) goto l207; if (!yy_FILTERFACTOR(yy)) goto l207; yyDo(yy, yySet, -1, 0); yyDo(yy, yy_3_FILTEREXPR, yy->__begin, yy->__end); goto l206; + l207:; yy->__pos= yypos207; yy->__thunkpos= yythunkpos207; + } yyDo(yy, yy_4_FILTEREXPR, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "FILTEREXPR", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 3, 0); + return 1; + l205:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "FILTEREXPR", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_FILTEREXPR_PK(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 2, 0); + yyprintf((stderr, "%s\n", "FILTEREXPR_PK")); + { int yypos209= yy->__pos, yythunkpos209= yy->__thunkpos; if (!yy_FILTERANCHOR(yy)) goto l209; yyDo(yy, yySet, -2, 0); yyDo(yy, yy_1_FILTEREXPR_PK, yy->__begin, yy->__end); goto l210; + l209:; yy->__pos= yypos209; yy->__thunkpos= yythunkpos209; + } + l210:; if (!yymatchChar(yy, '/')) goto l208; if (!yy__(yy)) goto l208; if (!yymatchChar(yy, '=')) goto l208; if (!yy__(yy)) goto l208; + { int yypos211= yy->__pos, yythunkpos211= yy->__thunkpos; if (!yy_PLACEHOLDER(yy)) goto l212; yyDo(yy, yySet, -1, 0); goto l211; + l212:; yy->__pos= yypos211; yy->__thunkpos= yythunkpos211; if (!yy_NUMPK(yy)) goto l213; yyDo(yy, yySet, -1, 0); goto l211; + l213:; yy->__pos= yypos211; yy->__thunkpos= yythunkpos211; if (!yy_NUMPK_ARR(yy)) goto l208; yyDo(yy, yySet, -1, 0); + } + l211:; yyDo(yy, yy_2_FILTEREXPR_PK, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "FILTEREXPR_PK", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 2, 0); + return 1; + l208:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "FILTEREXPR_PK", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_EOF(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "EOF")); + { int yypos215= yy->__pos, yythunkpos215= yy->__thunkpos; if (!yymatchDot(yy)) goto l215; goto l214; + l215:; yy->__pos= yypos215; yy->__thunkpos= yythunkpos215; + } + yyprintf((stderr, " ok %s @ %s\n", "EOF", yy->__buf+yy->__pos)); + return 1; + l214:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "EOF", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_OPTS(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "OPTS")); if (!yymatchChar(yy, '|')) goto l216; if (!yy__(yy)) goto l216; if (!yy_OPT(yy)) goto l216; + l217:; + { int yypos218= yy->__pos, yythunkpos218= yy->__thunkpos; if (!yy___(yy)) goto l218; if (!yy_OPT(yy)) goto l218; goto l217; + l218:; yy->__pos= yypos218; yy->__thunkpos= yythunkpos218; + } + yyprintf((stderr, " ok %s @ %s\n", "OPTS", yy->__buf+yy->__pos)); + return 1; + l216:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "OPTS", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_PROJECTION(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 2, 0); + yyprintf((stderr, "%s\n", "PROJECTION")); if (!yymatchChar(yy, '|')) goto l219; if (!yy__(yy)) goto l219; if (!yy_PROJNODES(yy)) goto l219; yyDo(yy, yySet, -2, 0); yyDo(yy, yy_1_PROJECTION, yy->__begin, yy->__end); + l220:; + { int yypos221= yy->__pos, yythunkpos221= yy->__thunkpos; if (!yy__(yy)) goto l221; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l221; +#undef yytext +#undef yyleng + } if (!yy_PROJOIN(yy)) goto l221; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l221; +#undef yytext +#undef yyleng + } yyDo(yy, yy_2_PROJECTION, yy->__begin, yy->__end); if (!yy__(yy)) goto l221; if (!yy_PROJNODES(yy)) goto l221; yyDo(yy, yySet, -1, 0); yyDo(yy, yy_3_PROJECTION, yy->__begin, yy->__end); goto l220; + l221:; yy->__pos= yypos221; yy->__thunkpos= yythunkpos221; + } yyDo(yy, yy_4_PROJECTION, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "PROJECTION", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 2, 0); + return 1; + l219:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "PROJECTION", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_UPSERT(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "UPSERT")); if (!yymatchString(yy, "upsert")) goto l222; if (!yy___(yy)) goto l222; + { int yypos223= yy->__pos, yythunkpos223= yy->__thunkpos; if (!yy_PLACEHOLDER(yy)) goto l224; goto l223; + l224:; yy->__pos= yypos223; yy->__thunkpos= yythunkpos223; if (!yy_OBJJ(yy)) goto l225; goto l223; + l225:; yy->__pos= yypos223; yy->__thunkpos= yythunkpos223; if (!yy_ARRJ(yy)) goto l222; + } + l223:; + yyprintf((stderr, " ok %s @ %s\n", "UPSERT", yy->__buf+yy->__pos)); + return 1; + l222:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "UPSERT", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_APPLY(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "APPLY")); if (!yymatchString(yy, "apply")) goto l226; if (!yy___(yy)) goto l226; + { int yypos227= yy->__pos, yythunkpos227= yy->__thunkpos; if (!yy_PLACEHOLDER(yy)) goto l228; goto l227; + l228:; yy->__pos= yypos227; yy->__thunkpos= yythunkpos227; if (!yy_OBJJ(yy)) goto l229; goto l227; + l229:; yy->__pos= yypos227; yy->__thunkpos= yythunkpos227; if (!yy_ARRJ(yy)) goto l226; + } + l227:; + yyprintf((stderr, " ok %s @ %s\n", "APPLY", yy->__buf+yy->__pos)); + return 1; + l226:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "APPLY", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy__(yycontext *yy) +{ + yyprintf((stderr, "%s\n", "_")); + l231:; + { int yypos232= yy->__pos, yythunkpos232= yy->__thunkpos; if (!yy_SPACE(yy)) goto l232; goto l231; + l232:; yy->__pos= yypos232; yy->__thunkpos= yythunkpos232; + } + yyprintf((stderr, " ok %s @ %s\n", "_", yy->__buf+yy->__pos)); + return 1; +} +YY_RULE(int) yy_QEXPR(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "QEXPR")); + { int yypos234= yy->__pos, yythunkpos234= yy->__thunkpos; if (!yy_FILTEREXPR_PK(yy)) goto l235; goto l234; + l235:; yy->__pos= yypos234; yy->__thunkpos= yythunkpos234; if (!yy_FILTEREXPR(yy)) goto l233; + } + l234:; + yyprintf((stderr, " ok %s @ %s\n", "QEXPR", yy->__buf+yy->__pos)); + return 1; + l233:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "QEXPR", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_QUERY(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; yyDo(yy, yyPush, 4, 0); + yyprintf((stderr, "%s\n", "QUERY")); if (!yy_QEXPR(yy)) goto l236; yyDo(yy, yySet, -4, 0); yyDo(yy, yy_1_QUERY, yy->__begin, yy->__end); + { int yypos237= yy->__pos, yythunkpos237= yy->__thunkpos; if (!yy__(yy)) goto l237; if (!yymatchChar(yy, '|')) goto l237; if (!yy__(yy)) goto l237; + { int yypos239= yy->__pos, yythunkpos239= yy->__thunkpos; if (!yy_APPLY(yy)) goto l240; yyDo(yy, yySet, -3, 0); yyDo(yy, yy_2_QUERY, yy->__begin, yy->__end); goto l239; + l240:; yy->__pos= yypos239; yy->__thunkpos= yythunkpos239; if (!yymatchString(yy, "del")) goto l241; yyDo(yy, yy_3_QUERY, yy->__begin, yy->__end); goto l239; + l241:; yy->__pos= yypos239; yy->__thunkpos= yythunkpos239; if (!yy_UPSERT(yy)) goto l237; yyDo(yy, yySet, -2, 0); yyDo(yy, yy_4_QUERY, yy->__begin, yy->__end); + } + l239:; goto l238; + l237:; yy->__pos= yypos237; yy->__thunkpos= yythunkpos237; + } + l238:; + { int yypos242= yy->__pos, yythunkpos242= yy->__thunkpos; if (!yy__(yy)) goto l242; if (!yy_PROJECTION(yy)) goto l242; yyDo(yy, yySet, -1, 0); yyDo(yy, yy_5_QUERY, yy->__begin, yy->__end); goto l243; + l242:; yy->__pos= yypos242; yy->__thunkpos= yythunkpos242; + } + l243:; + { int yypos244= yy->__pos, yythunkpos244= yy->__thunkpos; if (!yy__(yy)) goto l244; if (!yy_OPTS(yy)) goto l244; goto l245; + l244:; yy->__pos= yypos244; yy->__thunkpos= yythunkpos244; + } + l245:; if (!yy__(yy)) goto l236; if (!yy_EOF(yy)) goto l236; yyDo(yy, yy_6_QUERY, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "QUERY", yy->__buf+yy->__pos)); yyDo(yy, yyPop, 4, 0); + return 1; + l236:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "QUERY", yy->__buf+yy->__pos)); + return 0; +} + +#ifndef YY_PART + +typedef int (*yyrule)(yycontext *yy); + +YY_PARSE(int) YYPARSEFROM(YY_CTX_PARAM_ yyrule yystart) +{ + int yyok; + if (!yyctx->__buflen) + { + yyctx->__buflen= YY_BUFFER_SIZE; + yyctx->__buf= (char *)YY_MALLOC(yyctx, yyctx->__buflen); + yyctx->__textlen= YY_BUFFER_SIZE; + yyctx->__text= (char *)YY_MALLOC(yyctx, yyctx->__textlen); + yyctx->__thunkslen= YY_STACK_SIZE; + yyctx->__thunks= (yythunk *)YY_MALLOC(yyctx, sizeof(yythunk) * yyctx->__thunkslen); + yyctx->__valslen= YY_STACK_SIZE; + yyctx->__vals= (YYSTYPE *)YY_MALLOC(yyctx, sizeof(YYSTYPE) * yyctx->__valslen); + yyctx->__begin= yyctx->__end= yyctx->__pos= yyctx->__limit= yyctx->__thunkpos= 0; + } + yyctx->__begin= yyctx->__end= yyctx->__pos; + yyctx->__thunkpos= 0; + yyctx->__val= yyctx->__vals; + yyok= yystart(yyctx); + if (yyok) yyDone(yyctx); + yyCommit(yyctx); + return yyok; +} + +YY_PARSE(int) YYPARSE(YY_CTX_PARAM) +{ + return YYPARSEFROM(YY_CTX_ARG_ yy_QUERY); +} + +YY_PARSE(yycontext *) YYRELEASE(yycontext *yyctx) +{ + if (yyctx->__buflen) + { + yyctx->__buflen= 0; + YY_FREE(yyctx, yyctx->__buf); + YY_FREE(yyctx, yyctx->__text); + YY_FREE(yyctx, yyctx->__thunks); + YY_FREE(yyctx, yyctx->__vals); + } + return yyctx; +} + +#endif +#line 253 "./jqp.leg" + + +#include "./inc/jqpx.c" diff --git a/src/jql/jqp.h b/src/jql/jqp.h new file mode 100644 index 0000000..af09b8b --- /dev/null +++ b/src/jql/jqp.h @@ -0,0 +1,310 @@ +// -V::802 +#pragma once +#ifndef JQP_H +#define JQP_H + +/************************************************************************************************** + * EJDB2 + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +#include "jql.h" +#include "jbl.h" +#include +#include +#include +#include +#include + +typedef uint16_t jqp_string_flavours_t; +/** Query string parameter placeholder */ +#define JQP_STR_PLACEHOLDER ((jqp_string_flavours_t) 0x01U) +/** Query filter anchor */ +#define JQP_STR_ANCHOR ((jqp_string_flavours_t) 0x02U) +/** Projection field **/ +#define JQP_STR_PROJFIELD ((jqp_string_flavours_t) 0x04U) +/** Projection alias (all) **/ +#define JQP_STR_PROJALIAS ((jqp_string_flavours_t) 0x08U) +/** String is quoted */ +#define JQP_STR_QUOTED ((jqp_string_flavours_t) 0x10U) +/** Star (*) string */ +#define JQP_STR_STAR ((jqp_string_flavours_t) 0x20U) +/** Boolean negation/mode applied to it */ +#define JQP_STR_NEGATE ((jqp_string_flavours_t) 0x40U) +/** Double star (**) string */ +#define JQP_STR_DBL_STAR ((jqp_string_flavours_t) 0x80U) +/** Projection JOIN */ +#define JQP_STR_PROJOIN ((jqp_string_flavours_t) 0x100U) + + +typedef uint8_t jqp_int_flavours_t; +#define JQP_INT_SKIP ((jqp_int_flavours_t) 0x01U) +#define JQP_INT_LIMIT ((jqp_int_flavours_t) 0x02U) + +typedef enum { + JQP_QUERY_TYPE = 1, + JQP_EXPR_NODE_TYPE, + JQP_FILTER_TYPE, + JQP_NODE_TYPE, + JQP_EXPR_TYPE, + JQP_STRING_TYPE, + JQP_INTEGER_TYPE, + JQP_DOUBLE_TYPE, + JQP_OP_TYPE, + JQP_JOIN_TYPE, + JQP_PROJECTION_TYPE, + JQP_JSON_TYPE, +} jqp_unit_t; + +typedef enum { + JQP_NODE_FIELD = 1, + JQP_NODE_ANY, + JQP_NODE_ANYS, + JQP_NODE_EXPR, +} jqp_node_type_t; + +typedef enum { + // Do not reorder members + JQP_JOIN_AND = 1, + JQP_JOIN_OR, + JQP_OP_EQ, + JQP_OP_GT, + JQP_OP_GTE, + JQP_OP_LT, + JQP_OP_LTE, + JQP_OP_IN, + JQP_OP_NI, + JQP_OP_RE, + JQP_OP_PREFIX, +} jqp_op_t; + +struct JQP_AUX; + +typedef union _JQP_UNIT JQPUNIT; + +#define JQP_EXPR_NODE_FLAG_PK 0x01U + +#define JQP_EXPR_NODE_HEAD \ + jqp_unit_t type; \ + struct JQP_EXPR_NODE *next; \ + struct JQP_JOIN *join; \ + void *opaque; \ + uint8_t flags; + +typedef struct JQP_EXPR_NODE { // Base for JQP_FILTER + JQP_EXPR_NODE_HEAD + struct JQP_EXPR_NODE *chain; +} JQP_EXPR_NODE; + +typedef struct JQP_EXPR_NODE_PK { + JQP_EXPR_NODE_HEAD + struct JQP_EXPR_NODE *chain; // Not used, plased for JQP_EXPR_NODE compatibility + const char *anchor; + JQPUNIT *argument; +} JQP_EXPR_NODE_PK; + +typedef struct JQP_FILTER { + JQP_EXPR_NODE_HEAD + const char *anchor; + struct JQP_NODE *node; +} JQP_FILTER; + +typedef struct JQP_JSON { + jqp_unit_t type; + struct _JBL_NODE jn; + void *opaque; +} JQP_JSON; + +typedef struct JQP_NODE { + jqp_unit_t type; + jqp_node_type_t ntype; + struct JQP_NODE *next; + JQPUNIT *value; + int start; // Used in query matching + int end; // Used in query matching +} JQP_NODE; + +typedef struct JQP_STRING { + jqp_unit_t type; + jqp_string_flavours_t flavour; + const char *value; + struct JQP_STRING *next; + struct JQP_STRING *subnext; + struct JQP_STRING *placeholder_next; + void *opaque; +} JQP_STRING; + +typedef struct JQP_INTEGER { + jqp_unit_t type; + jqp_int_flavours_t flavour; + int64_t value; + void *opaque; +} JQP_INTEGER; + +typedef struct JQP_DOUBLE { + jqp_unit_t type; + jqp_int_flavours_t flavour; + double value; + void *opaque; +} JQP_DOUBLE; + +typedef struct JQP_OP { + jqp_unit_t type; + bool negate; + jqp_op_t value; + struct JQP_OP *next; + void *opaque; +} JQP_OP; + +typedef struct JQP_JOIN { + jqp_unit_t type; + bool negate; + jqp_op_t value; +} JQP_JOIN; + +typedef struct JQP_EXPR { + jqp_unit_t type; + struct JQP_JOIN *join; + struct JQP_OP *op; + JQPUNIT *left; + JQPUNIT *right; + struct JQP_EXPR *next; + bool prematched; +} JQP_EXPR; + +typedef struct JQP_PROJECTION { + jqp_unit_t type; + struct JQP_STRING *value; + struct JQP_PROJECTION *next; + int16_t pos; // Current matching position, used in jql.c#_jql_project + int16_t cnt; // Number of projection sections, used in jql.c#_jql_project + +#define JQP_PROJECTION_FLAG_EXCLUDE 0x01U +#define JQP_PROJECTION_FLAG_INCLUDE 0x02U +#define JQP_PROJECTION_FLAG_JOINS 0x04U + uint8_t flags; +} JQP_PROJECTION; + +typedef struct JQP_QUERY { + jqp_unit_t type; + struct JQP_AUX *aux; +} JQP_QUERY; + +//-- + +union _JQP_UNIT { + jqp_unit_t type; + struct JQP_QUERY query; + struct JQP_EXPR_NODE exprnode; + struct JQP_EXPR_NODE_PK exprnode_pk; + struct JQP_FILTER filter; + struct JQP_NODE node; + struct JQP_EXPR expr; + struct JQP_JOIN join; + struct JQP_OP op; + struct JQP_STRING string; + struct JQP_INTEGER intval; + struct JQP_DOUBLE dblval; + struct JQP_JSON json; + struct JQP_PROJECTION projection; +}; + +typedef enum { + STACK_UNIT = 1, + STACK_STRING, + STACK_INT, + STACK_FLOAT, +} jqp_stack_t; + +typedef struct JQP_STACK { + jqp_stack_t type; + struct JQP_STACK *next; + struct JQP_STACK *prev; + union { + JQPUNIT *unit; + char *str; + int64_t i64; + double f64; + }; +} JQP_STACK; + +#define JQP_AUX_STACKPOOL_NUM 128 + +typedef uint8_t jqp_query_mode_t; + +#define JQP_QRY_COUNT ((jqp_query_mode_t) 0x01U) +#define JQP_QRY_NOIDX ((jqp_query_mode_t) 0x02U) +#define JQP_QRY_APPLY_DEL ((jqp_query_mode_t) 0x04U) +#define JQP_QRY_INVERSE ((jqp_query_mode_t) 0x08U) +#define JQP_QRY_APPLY_UPSERT ((jqp_query_mode_t) 0x10U) + +#define JQP_QRY_AGGREGATE (JQP_QRY_COUNT) + +typedef struct JQP_AUX { + int pos; + int stackn; + int num_placeholders; + int orderby_num; /**< Number of order-by blocks */ + iwrc rc; + jmp_buf fatal_jmp; + const char *buf; + IWXSTR *xerr; + IWPOOL *pool; + struct JQP_QUERY *query; + JQP_STACK *stack; + jql_create_mode_t mode; + // + struct JQP_EXPR_NODE *expr; + struct JQP_PROJECTION *projection; + JQP_STRING *start_placeholder; + JQP_STRING *end_placeholder; + JQP_STRING *orderby; + JBL_PTR *orderby_ptrs; /**< Order-by pointers, orderby_num - number of pointers allocated */ + JQP_OP *start_op; + JQP_OP *end_op; + JQPUNIT *skip; + JQPUNIT *limit; + JBL_NODE apply; + const char *apply_placeholder; + const char *first_anchor; + jqp_query_mode_t qmode; + bool negate; + bool has_keep_projections; + bool has_exclude_all_projection; + JQP_STACK stackpool[JQP_AUX_STACKPOOL_NUM]; +} JQP_AUX; + +iwrc jqp_aux_create(JQP_AUX **auxp, const char *input); + +void jqp_aux_destroy(JQP_AUX **auxp); + +iwrc jqp_parse(JQP_AUX *aux); + +iwrc jqp_print_query(const JQP_QUERY *q, jbl_json_printer pt, void *op); + +iwrc jqp_alloc_orderby_pointers(const JQP_QUERY *q, JBL_PTR *optr, size_t *nptr); + +iwrc jqp_print_filter_node_expr(const JQP_EXPR *e, jbl_json_printer pt, void *op); + +#endif diff --git a/src/jql/jqp.leg b/src/jql/jqp.leg new file mode 100644 index 0000000..63eff7e --- /dev/null +++ b/src/jql/jqp.leg @@ -0,0 +1,255 @@ +%{ +#include "jqp.h" +#include "jbl.h" + + +#define YY_CTX_LOCAL 1 +#define YY_CTX_MEMBERS \ + JQP_AUX *aux; + +struct _yycontext; + +static void _jqp_finish(struct _yycontext *yy); +static void _jqp_debug(struct _yycontext *yy, const char *text); +static void *_jqp_malloc(struct _yycontext *yy, size_t size); +static void *_jqp_realloc(struct _yycontext *yy, void *ptr, size_t size); +static JQPUNIT *_jqp_unit(struct _yycontext *yy); + +static void _jqp_op_negate(struct _yycontext *yy); +static void _jqp_op_negate_reset(struct _yycontext *yy); + +static JQPUNIT *_jqp_string(struct _yycontext *yy, jqp_string_flavours_t flv, const char *text); +static JQPUNIT *_jqp_unescaped_string(struct _yycontext *yy, jqp_string_flavours_t flv, const char *text); +static JQPUNIT *_jqp_number(struct _yycontext *yy, jqp_int_flavours_t flv, const char *text); +static JQPUNIT *_jqp_placeholder(struct _yycontext *yy, const char *text); +static JQPUNIT *_jqp_unit_op(struct _yycontext *yy, const char *text); +static JQPUNIT *_jqp_unit_join(struct _yycontext *yy, const char *text); +static JQPUNIT *_jqp_expr(struct _yycontext *yy, JQPUNIT *left, JQPUNIT *op, JQPUNIT *right); +static JQPUNIT *_jqp_node(struct _yycontext *yy, JQPUNIT *value); +static JQPUNIT *_jqp_projection(struct _yycontext *yy, JQPUNIT *value, uint8_t flags); +static void _jqp_set_skip(struct _yycontext *yy, JQPUNIT *unit); +static void _jqp_set_limit(struct _yycontext *yy, JQPUNIT *unit); +static void _jqp_add_orderby(struct _yycontext *yy, JQPUNIT *unit); +static void _jqp_set_aggregate_count(struct _yycontext *yy); +static void _jqp_set_noidx(struct _yycontext *yy); +static void _jqp_set_inverse(struct _yycontext *yy); + +static JQPUNIT *_jqp_json_string(struct _yycontext *yy, const char *text); +static JQPUNIT *_jqp_json_number(struct _yycontext *yy, const char *text); +static JQPUNIT *_jqp_json_true_false_null(struct _yycontext *yy, const char *text); +static JQPUNIT *_jqp_json_pair(struct _yycontext *yy, JQPUNIT *key, JQPUNIT *val); +static JQPUNIT *_jqp_json_collect(struct _yycontext *yy, jbl_type_t type, JQPUNIT *until); + +static JQP_STACK *_jqp_push(struct _yycontext *yy); +static void _jqp_unit_push(struct _yycontext *yy, JQPUNIT *unit); +static JQPUNIT *_jqp_unit_pop(struct _yycontext *yy); +static void _jqp_string_push(struct _yycontext *yy, char *str, bool dup); +static char *_jqp_string_pop(struct _yycontext *yy); + +static JQPUNIT *_jqp_pop_filter_factor_chain(struct _yycontext *yy, JQPUNIT *until); +static JQPUNIT *_jqp_pop_expr_chain(struct _yycontext *yy, JQPUNIT *until); +static JQPUNIT *_jqp_pop_node_chain(struct _yycontext *yy, JQPUNIT *until); +static JQPUNIT *_jqp_pop_projfields_chain(struct _yycontext *yy, JQPUNIT *until); +static JQPUNIT *_jqp_pop_projection_nodes(struct _yycontext *yy, JQPUNIT *until); +static JQPUNIT *_jqp_pop_ordernodes(struct _yycontext *yy, JQPUNIT *until); +static JQPUNIT *_jqp_push_joined_projection(struct _yycontext *yy, JQPUNIT *p); +static JQPUNIT *_jqp_pop_joined_projections(struct _yycontext *yy, JQPUNIT *until); + +static void _jqp_set_filters_expr(struct _yycontext *yy, JQPUNIT *expr); +static JQPUNIT *_jqp_create_filterexpr_pk(struct _yycontext *yy, JQPUNIT *argument); +static void _jqp_set_apply(struct _yycontext *yy, JQPUNIT *unit); +static void _jqp_set_apply_delete(struct _yycontext *yy); +static void _jqp_set_apply_upsert(struct _yycontext *yy, JQPUNIT *unit); +static void _jqp_set_projection(struct _yycontext *yy, JQPUNIT *unit); + +#define YYSTYPE JQPUNIT* +#define YY_MALLOC(yy_, sz_) _jqp_malloc(yy_, sz_) +#define YY_REALLOC(yy_, ptr_, sz_) _jqp_realloc(yy_, ptr_, sz_) + +#define YY_INPUT(yy_, buf_, result_, max_size_) \ + { \ + JQP_AUX *aux = (yy_)->aux; \ + if (aux->rc || *(aux->buf + aux->pos) == '\0') { \ + result_ = 0; \ + } else { \ + char ch = *(aux->buf + aux->pos++); \ + result_ = 1; \ + *(buf_)= ch; \ + } \ + } +%} + +QUERY = s:QEXPR { _jqp_set_filters_expr(yy, s); } + (_ '|' _ (a:APPLY { _jqp_set_apply(yy, a); } | "del" { _jqp_set_apply_delete(yy); } | u:UPSERT { _jqp_set_apply_upsert(yy, u); } ) )? + (_ p:PROJECTION { _jqp_set_projection(yy, p); } )? + (_ OPTS )? + _ EOF { _jqp_finish(yy); } + +QEXPR = FILTEREXPR_PK | FILTEREXPR + +FILTEREXPR_PK = (a:FILTERANCHOR { _jqp_unit_push(yy, a); })? + '/' _ '=' _ ( p:PLACEHOLDER | p:NUMPK | p:NUMPK_ARR ) + { $$ = _jqp_create_filterexpr_pk(yy, p) } + +FILTERJOIN = (<("and" | "or")> (__ "not" { _jqp_op_negate(yy); })?) { $$ = _jqp_unit_join(yy, yytext); } + +APPLY = "apply" __ ( PLACEHOLDER | OBJJ | ARRJ ) + +UPSERT = "upsert" __ ( PLACEHOLDER | OBJJ | ARRJ ) + +PROJECTION = '|' _ (sn:PROJNODES { _jqp_unit_push(yy, sn); } + (_ { _jqp_string_push(yy, yytext, true); } _ n:PROJNODES { _jqp_push_joined_projection(yy, n); } )*) + { $$ = _jqp_pop_joined_projections(yy, sn); } + +OPTS = '|' _ OPT (__ OPT)* + +OPT = SKIP | LIMIT | ORDERBY | COUNT | NOIDX | INVERSE + +SKIP = "skip" __ ( { $$ = _jqp_number(yy, JQP_INT_SKIP, yytext); } | p:PLACEHOLDER { $$ = p; }) { _jqp_set_skip(yy, $$); } + +LIMIT = "limit" __ ( { $$ = _jqp_number(yy, JQP_INT_LIMIT, yytext); } | p:PLACEHOLDER { $$ = p; }) { _jqp_set_limit(yy, $$); } + +COUNT = "count" { _jqp_set_aggregate_count(yy); } + +NOIDX = "noidx" { _jqp_set_noidx(yy); } + +INVERSE = "inverse" { _jqp_set_inverse(yy); } + +ORDERBY = ("asc" | "desc" { _jqp_op_negate(yy); }) + __ ( p:ORDERNODES | p:PLACEHOLDER ) + { p->string.flavour |= (yy->aux->negate ? JQP_STR_NEGATE : 0); _jqp_op_negate_reset(yy); _jqp_add_orderby(yy, p); } + +ORDERNODES = sn:ORDERNODE { _jqp_unit_push(yy, sn); } (n:ORDERNODE { _jqp_unit_push(yy, n); })* { $$ = _jqp_pop_ordernodes(yy, sn) } + +ORDERNODE = '/' PROJPROP + +PROJNODES = (a:PROJALL { $$ = _jqp_projection(yy, a, 0); } + | (sn:PROJNODE { _jqp_unit_push(yy, sn); } (n:PROJNODE { _jqp_unit_push(yy, n);})*) { $$ = _jqp_pop_projection_nodes(yy, sn); }) + +PROJALL = "all" { $$ = _jqp_string(yy, JQP_STR_PROJALIAS, "all"); } + +PROJNODE = '/' (PROJFIELDS | PROJPROP) + +PROJFIELDS = ('{' _ sp:PROJPROP { _jqp_unit_push(yy, sp); } (_ ',' _ p:PROJPROP { _jqp_unit_push(yy, p); } )* _ '}') + { $$ = _jqp_pop_projfields_chain(yy, sp); } + +PROJPROP = STRN | PSTRP + +PROJOIN = ('+' | '-') + +PSTRP = { $$ = _jqp_string(yy, 0, yytext); } + +PCHP = '\\' '\\' + | '\\' [bfnrt] + | '\\' 'u' HEX HEX HEX HEX + | !["/{},\t\n\r ] . + + +FILTERFACTOR = (FILTER | '(' FILTEREXPR ')') + +FILTEREXPR = ff:FILTERFACTOR { _jqp_unit_push(yy, ff); } + (__ j:FILTERJOIN { _jqp_unit_push(yy, j); } __ f:FILTERFACTOR { _jqp_unit_push(yy, f); })* { $$ = _jqp_pop_filter_factor_chain(yy, ff); } + +FILTER = ((a:FILTERANCHOR { _jqp_unit_push(yy, a); })? fn:NODE { _jqp_unit_push(yy, fn); } (n:NODE { _jqp_unit_push(yy, n); })*) { $$ = _jqp_pop_node_chain(yy, fn); } + +FILTERANCHOR = '@' <([a-zA-Z0-9_\-]+)> { $$ = _jqp_string(yy, JQP_STR_ANCHOR, yytext); } + + +NODE = '/' (n:STRN | n:NEXPR | n:STRP) { $$ = _jqp_node(yy, n); } + +NEXPR = ('[' _ n:NEXPAIR { _jqp_unit_push(yy, n); } + ( __ j:NEXJOIN { _jqp_unit_push(yy, j); } __ np:NEXPAIR { _jqp_unit_push(yy, np); })* _ ']') + { $$ = _jqp_pop_expr_chain(yy, n); } + +NEXJOIN = (<("and" | "or")> (__ "not" { _jqp_op_negate(yy); })?) { $$ = _jqp_unit_join(yy, yytext); } + +NEXPAIR = (l:NEXLEFT _ o:NEXOP _ r:NEXRIGHT) { $$ = _jqp_expr(yy, l, o, r); } + +NEXLEFT = (DBLSTAR | STRSTAR | STRN | NEXPRLEFT | STRP) + +NEXPRLEFT = ('[' _ l:STRSTAR _ o:NEXOP _ r:NEXRIGHT _ ']') { $$ = _jqp_expr(yy, l, o, r); } + +NEXRIGHT = (PLACEHOLDER | VALJ | STRP) + +PLACEHOLDER = ':' <([a-zA-Z0-9]+ | '?')> { $$ = _jqp_placeholder(yy, yytext); } + +NEXOP = ("not" __ { _jqp_op_negate(yy); })? <("in" | "ni" | "re")> { $$ = _jqp_unit_op(yy, yytext); } + | <(">=" | "gte")> { $$ = _jqp_unit_op(yy, yytext); } + | <("<=" | "lte")> { $$ = _jqp_unit_op(yy, yytext); } + | ('!' _ { _jqp_op_negate(yy); })? <('=' | "eq" | '~')> { $$ = _jqp_unit_op(yy, yytext); } + | <('>' | "gt")> { $$ = _jqp_unit_op(yy, yytext); } + | <('<' | "lt")> { $$ = _jqp_unit_op(yy, yytext); } + | <('~')> { $$ = _jqp_unit_op(yy, yytext); } + +STRP = { $$ = _jqp_unescaped_string(yy, 0, yytext); } + +DBLSTAR = "**" { $$ = _jqp_unescaped_string(yy, JQP_STR_DBL_STAR, "**"); } + +STRSTAR = "*" { $$ = _jqp_unescaped_string(yy, JQP_STR_STAR, "*"); } + +STRN = '"' '"' { $$ = _jqp_unescaped_string(yy, JQP_STR_QUOTED, yytext); } + +OBJJ = (s:SOBJJ { _jqp_unit_push(yy, s); } + _ (fp:PAIRJ { _jqp_unit_push(yy, fp); } (_ ',' _ p:PAIRJ { _jqp_unit_push(yy, p); })* )? _ '}') + { $$ = _jqp_json_collect(yy, JBV_OBJECT, s); } + +ARRJ = (s:SARRJ { _jqp_unit_push(yy, s); } + _ (fv:VALJ { _jqp_unit_push(yy, fv); } (_ ',' _ v:VALJ { _jqp_unit_push(yy, v); })* )? _ ']') + { $$ = _jqp_json_collect(yy, JBV_ARRAY, s); } + +SOBJJ = '{' { $$ = _jqp_unit(yy); } + +SARRJ = '[' { $$ = _jqp_unit(yy); } + +PAIRJ = (s:STRJ _ ':' _ v:VALJ) { $$ = _jqp_json_pair(yy, s, v); } + +VALJ = STRJ + | NUMJ + | OBJJ + | ARRJ + | "true" { $$ = _jqp_json_true_false_null(yy, "true"); } + | "false" { $$ = _jqp_json_true_false_null(yy, "false"); } + | "null" { $$ = _jqp_json_true_false_null(yy, "null"); } + +STRJ = '"' <(CHJ*)> '"' { $$ = _jqp_json_string(yy, yytext); } + +HEX = [0-9A-Fa-f] + +CHJ = '\\' '"' + | '\\' '\\' + | '\\' [bfnrt] + | '\\' 'u' HEX HEX HEX HEX + | !'"' . + +CHP = '\\' '\\' + | '\\' [bfnrt] + | '\\' 'u' HEX HEX HEX HEX + | ![/"[\]=> { $$ = _jqp_json_number(yy, yytext); } + +NUMPK = <(NUMI)> { $$ = _jqp_json_number(yy, yytext); } + +NUMPK_ARR = (s:SARRJ { _jqp_unit_push(yy, s); } + _ (fv:NUMPK { _jqp_unit_push(yy, fv); } (_ ',' _ v:NUMPK { _jqp_unit_push(yy, v); })* )? _ ']') + { $$ = _jqp_json_collect(yy, JBV_ARRAY, s); } + +NUMI = '0' | [1-9] [0-9]* + +NUMF = '.' [0-9]+ + +NUME = [eE] [+-]? [0-9]+ + +_ = SPACE* + +__ = SPACE+ + +SPACE = ' ' | '\t' | EOL + +EOL = ('\r\n' | '\n' | '\r') + +EOF = !. + +%% + +#include "./inc/jqpx.c" \ No newline at end of file diff --git a/src/jql/tests/CMakeLists.txt b/src/jql/tests/CMakeLists.txt new file mode 100644 index 0000000..be644db --- /dev/null +++ b/src/jql/tests/CMakeLists.txt @@ -0,0 +1,18 @@ +link_libraries(ejdb2_s ${CUNIT_LIBRARIES}) +include_directories(${CUNIT_INCLUDE_DIRS}) + +file(GLOB datafiles RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/data" "data/*") +foreach (file ${datafiles}) + configure_file("data/${file}" "data/${file}" COPYONLY) +endforeach () + +set(TEST_DATA_DIR ${CMAKE_CURRENT_BINARY_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${TEST_DATA_DIR}) + +foreach (TN IN ITEMS jql_test1) + add_executable(${TN} ${TN}.c) + set_target_properties(${TN} PROPERTIES + COMPILE_FLAGS "-DIW_STATIC") + add_test(NAME ${TN} WORKING_DIRECTORY ${TEST_DATA_DIR} + COMMAND ${TEST_TOOL_CMD} $) +endforeach () diff --git a/src/jql/tests/data/000.expected.jql b/src/jql/tests/data/000.expected.jql new file mode 100644 index 0000000..01e1fae --- /dev/null +++ b/src/jql/tests/data/000.expected.jql @@ -0,0 +1 @@ +/foo/bar and /foo/baz \ No newline at end of file diff --git a/src/jql/tests/data/000.jql b/src/jql/tests/data/000.jql new file mode 100644 index 0000000..01e1fae --- /dev/null +++ b/src/jql/tests/data/000.jql @@ -0,0 +1 @@ +/foo/bar and /foo/baz \ No newline at end of file diff --git a/src/jql/tests/data/001.expected.jql b/src/jql/tests/data/001.expected.jql new file mode 100644 index 0000000..fa61707 --- /dev/null +++ b/src/jql/tests/data/001.expected.jql @@ -0,0 +1,3 @@ +@one/**/[familyName re "D\n.*"] and /**/family/mother/[age > 30 and age <= 40 or name re "Grace.*"] and not /bar/ba z"zz +| apply {"foo":"bar","nums":[1,2,3,4,5]} +| all - /**/author/{givenName,familyName} \ No newline at end of file diff --git a/src/jql/tests/data/001.jql b/src/jql/tests/data/001.jql new file mode 100644 index 0000000..bdd1e19 --- /dev/null +++ b/src/jql/tests/data/001.jql @@ -0,0 +1,5 @@ +@one/**/[familyName re "D\n.*"] +and /**/family/mother/[age > 30 and age <= 40 or name re "Grace.*"] +and not /bar/"ba z\"zz" +| apply {"foo":"bar", "nums": [1,2,3,4,5]} +| all - /**/author/{givenName,familyName} diff --git a/src/jql/tests/data/002.expected.jql b/src/jql/tests/data/002.expected.jql new file mode 100644 index 0000000..b638d5e --- /dev/null +++ b/src/jql/tests/data/002.expected.jql @@ -0,0 +1 @@ +/foo/bar \ No newline at end of file diff --git a/src/jql/tests/data/002.jql b/src/jql/tests/data/002.jql new file mode 100644 index 0000000..66fa850 --- /dev/null +++ b/src/jql/tests/data/002.jql @@ -0,0 +1 @@ +/foo/bar diff --git a/src/jql/tests/data/003.expected.jql b/src/jql/tests/data/003.expected.jql new file mode 100644 index 0000000..9ef486a --- /dev/null +++ b/src/jql/tests/data/003.expected.jql @@ -0,0 +1 @@ +/foo/b ar \ No newline at end of file diff --git a/src/jql/tests/data/003.jql b/src/jql/tests/data/003.jql new file mode 100644 index 0000000..35e0e8f --- /dev/null +++ b/src/jql/tests/data/003.jql @@ -0,0 +1 @@ +/"foo"/"b ar" diff --git a/src/jql/tests/data/004.expected.jql b/src/jql/tests/data/004.expected.jql new file mode 100644 index 0000000..36c4c44 --- /dev/null +++ b/src/jql/tests/data/004.expected.jql @@ -0,0 +1 @@ +/foo and /bar \ No newline at end of file diff --git a/src/jql/tests/data/004.jql b/src/jql/tests/data/004.jql new file mode 100644 index 0000000..36c4c44 --- /dev/null +++ b/src/jql/tests/data/004.jql @@ -0,0 +1 @@ +/foo and /bar \ No newline at end of file diff --git a/src/jql/tests/data/005.expected.jql b/src/jql/tests/data/005.expected.jql new file mode 100644 index 0000000..c57442e --- /dev/null +++ b/src/jql/tests/data/005.expected.jql @@ -0,0 +1 @@ +/foo/[bar = "val"] \ No newline at end of file diff --git a/src/jql/tests/data/005.jql b/src/jql/tests/data/005.jql new file mode 100644 index 0000000..eb4a6d0 --- /dev/null +++ b/src/jql/tests/data/005.jql @@ -0,0 +1 @@ +/foo/[bar = "val"] diff --git a/src/jql/tests/data/006.expected.jql b/src/jql/tests/data/006.expected.jql new file mode 100644 index 0000000..73e8890 --- /dev/null +++ b/src/jql/tests/data/006.expected.jql @@ -0,0 +1 @@ +/foo/[bar = :placeholder] \ No newline at end of file diff --git a/src/jql/tests/data/006.jql b/src/jql/tests/data/006.jql new file mode 100644 index 0000000..a067d3e --- /dev/null +++ b/src/jql/tests/data/006.jql @@ -0,0 +1 @@ +/foo/[bar = :placeholder] diff --git a/src/jql/tests/data/007.expected.jql b/src/jql/tests/data/007.expected.jql new file mode 100644 index 0000000..f5e3a95 --- /dev/null +++ b/src/jql/tests/data/007.expected.jql @@ -0,0 +1 @@ +/foo/[bar = :? and "baz" = :?] or /root/**/[fname not re "John"] \ No newline at end of file diff --git a/src/jql/tests/data/007.jql b/src/jql/tests/data/007.jql new file mode 100644 index 0000000..f5e3a95 --- /dev/null +++ b/src/jql/tests/data/007.jql @@ -0,0 +1 @@ +/foo/[bar = :? and "baz" = :?] or /root/**/[fname not re "John"] \ No newline at end of file diff --git a/src/jql/tests/data/008.expected.jql b/src/jql/tests/data/008.expected.jql new file mode 100644 index 0000000..c2b431c --- /dev/null +++ b/src/jql/tests/data/008.expected.jql @@ -0,0 +1 @@ +/foo/[bar = [1,2,3,{},{"foo":"bar"}]] \ No newline at end of file diff --git a/src/jql/tests/data/008.jql b/src/jql/tests/data/008.jql new file mode 100644 index 0000000..8cd2cf1 --- /dev/null +++ b/src/jql/tests/data/008.jql @@ -0,0 +1 @@ +/foo/[bar = [1, 2,3,{},{"foo": "bar"}]] diff --git a/src/jql/tests/data/009.expected.jql b/src/jql/tests/data/009.expected.jql new file mode 100644 index 0000000..cd97359 --- /dev/null +++ b/src/jql/tests/data/009.expected.jql @@ -0,0 +1 @@ +/tags/[* in ["sample","foo"] and * re "ta.*"] \ No newline at end of file diff --git a/src/jql/tests/data/009.jql b/src/jql/tests/data/009.jql new file mode 100644 index 0000000..311905a --- /dev/null +++ b/src/jql/tests/data/009.jql @@ -0,0 +1 @@ +/tags/[* in ["sample", "foo"] and * re "ta.*"] \ No newline at end of file diff --git a/src/jql/tests/data/010.expected.jql b/src/jql/tests/data/010.expected.jql new file mode 100644 index 0000000..42a9c96 --- /dev/null +++ b/src/jql/tests/data/010.expected.jql @@ -0,0 +1 @@ +/**/[[* = "familyName"] = "Doe"] \ No newline at end of file diff --git a/src/jql/tests/data/010.jql b/src/jql/tests/data/010.jql new file mode 100644 index 0000000..965dfe8 --- /dev/null +++ b/src/jql/tests/data/010.jql @@ -0,0 +1 @@ +/**/[[* = "familyName"] = "Doe"] diff --git a/src/jql/tests/data/011.jql b/src/jql/tests/data/011.jql new file mode 100644 index 0000000..57eb572 --- /dev/null +++ b/src/jql/tests/data/011.jql @@ -0,0 +1 @@ +/foo/[barlike22] diff --git a/src/jql/tests/data/012.jql b/src/jql/tests/data/012.jql new file mode 100644 index 0000000..62e20e7 --- /dev/null +++ b/src/jql/tests/data/012.jql @@ -0,0 +1 @@ +/foo/\"bar \ No newline at end of file diff --git a/src/jql/tests/data/013.jql b/src/jql/tests/data/013.jql new file mode 100644 index 0000000..337ca42 --- /dev/null +++ b/src/jql/tests/data/013.jql @@ -0,0 +1 @@ +foo/bar \ No newline at end of file diff --git a/src/jql/tests/data/014.expected.jql b/src/jql/tests/data/014.expected.jql new file mode 100644 index 0000000..787c2b1 --- /dev/null +++ b/src/jql/tests/data/014.expected.jql @@ -0,0 +1 @@ +(/foo/bar) \ No newline at end of file diff --git a/src/jql/tests/data/014.jql b/src/jql/tests/data/014.jql new file mode 100644 index 0000000..787c2b1 --- /dev/null +++ b/src/jql/tests/data/014.jql @@ -0,0 +1 @@ +(/foo/bar) \ No newline at end of file diff --git a/src/jql/tests/data/015.expected.jql b/src/jql/tests/data/015.expected.jql new file mode 100644 index 0000000..641c5d1 --- /dev/null +++ b/src/jql/tests/data/015.expected.jql @@ -0,0 +1 @@ +/foo/bar and (/foo/baz or /foo/gaz) \ No newline at end of file diff --git a/src/jql/tests/data/015.jql b/src/jql/tests/data/015.jql new file mode 100644 index 0000000..641c5d1 --- /dev/null +++ b/src/jql/tests/data/015.jql @@ -0,0 +1 @@ +/foo/bar and (/foo/baz or /foo/gaz) \ No newline at end of file diff --git a/src/jql/tests/data/016.expected.jql b/src/jql/tests/data/016.expected.jql new file mode 100644 index 0000000..d566ea3 --- /dev/null +++ b/src/jql/tests/data/016.expected.jql @@ -0,0 +1 @@ +/foo/bar and (/foo/baz or (/foo/gaz and /foo/daz) and (/foo/sss or /foo/vvv)) \ No newline at end of file diff --git a/src/jql/tests/data/016.jql b/src/jql/tests/data/016.jql new file mode 100644 index 0000000..d566ea3 --- /dev/null +++ b/src/jql/tests/data/016.jql @@ -0,0 +1 @@ +/foo/bar and (/foo/baz or (/foo/gaz and /foo/daz) and (/foo/sss or /foo/vvv)) \ No newline at end of file diff --git a/src/jql/tests/data/017.expected.jql b/src/jql/tests/data/017.expected.jql new file mode 100644 index 0000000..c5cab4d --- /dev/null +++ b/src/jql/tests/data/017.expected.jql @@ -0,0 +1,2 @@ +/foo/bar +| skip 10 \ No newline at end of file diff --git a/src/jql/tests/data/017.jql b/src/jql/tests/data/017.jql new file mode 100644 index 0000000..cb2e886 --- /dev/null +++ b/src/jql/tests/data/017.jql @@ -0,0 +1 @@ +/foo/bar | skip 10 \ No newline at end of file diff --git a/src/jql/tests/data/018.expected.jql b/src/jql/tests/data/018.expected.jql new file mode 100644 index 0000000..564337b --- /dev/null +++ b/src/jql/tests/data/018.expected.jql @@ -0,0 +1,2 @@ +/foo/bar +| skip :numskip limit 1000 \ No newline at end of file diff --git a/src/jql/tests/data/018.jql b/src/jql/tests/data/018.jql new file mode 100644 index 0000000..d860e14 --- /dev/null +++ b/src/jql/tests/data/018.jql @@ -0,0 +1 @@ +/foo/bar | skip :numskip limit 1000 diff --git a/src/jql/tests/data/019.expected.jql b/src/jql/tests/data/019.expected.jql new file mode 100644 index 0000000..38c4798 --- /dev/null +++ b/src/jql/tests/data/019.expected.jql @@ -0,0 +1,6 @@ +/foo/bar +| apply {"foo":"bar","nums":[1,2,3,4,5]} +| all - /**/author/{givenName,familyName} +| asc /foo/bar + desc :myfield + skip :numskip limit 1000 \ No newline at end of file diff --git a/src/jql/tests/data/019.jql b/src/jql/tests/data/019.jql new file mode 100644 index 0000000..4261a59 --- /dev/null +++ b/src/jql/tests/data/019.jql @@ -0,0 +1,4 @@ +/foo/bar | apply {"foo":"bar","nums":[1,2,3,4,5]} +| all - /**/author/{givenName,familyName} +| asc /foo/bar desc :myfield + skip :numskip limit 1000 diff --git a/src/jql/tests/data/020.expected.jql b/src/jql/tests/data/020.expected.jql new file mode 100644 index 0000000..42ef03c --- /dev/null +++ b/src/jql/tests/data/020.expected.jql @@ -0,0 +1,2 @@ +/=:? +| apply {"foo":"bar","nums":[1,2,3,4,5]} \ No newline at end of file diff --git a/src/jql/tests/data/020.jql b/src/jql/tests/data/020.jql new file mode 100644 index 0000000..f054b50 --- /dev/null +++ b/src/jql/tests/data/020.jql @@ -0,0 +1 @@ +/= :? | apply {"foo":"bar","nums":[1,2,3,4,5]} \ No newline at end of file diff --git a/src/jql/tests/data/021.expected.jql b/src/jql/tests/data/021.expected.jql new file mode 100644 index 0000000..bb51d00 --- /dev/null +++ b/src/jql/tests/data/021.expected.jql @@ -0,0 +1,2 @@ +@users/=:id +| apply {"foo":"bar","nums":[1,2,3,4,5]} \ No newline at end of file diff --git a/src/jql/tests/data/021.jql b/src/jql/tests/data/021.jql new file mode 100644 index 0000000..4b563b0 --- /dev/null +++ b/src/jql/tests/data/021.jql @@ -0,0 +1 @@ +@users/= :id | apply {"foo":"bar","nums":[1,2,3,4,5]} \ No newline at end of file diff --git a/src/jql/tests/data/022.expected.jql b/src/jql/tests/data/022.expected.jql new file mode 100644 index 0000000..6cce421 --- /dev/null +++ b/src/jql/tests/data/022.expected.jql @@ -0,0 +1 @@ +@users/=122 \ No newline at end of file diff --git a/src/jql/tests/data/022.jql b/src/jql/tests/data/022.jql new file mode 100644 index 0000000..6cce421 --- /dev/null +++ b/src/jql/tests/data/022.jql @@ -0,0 +1 @@ +@users/=122 \ No newline at end of file diff --git a/src/jql/tests/jql_test1.c b/src/jql/tests/jql_test1.c new file mode 100644 index 0000000..c36d28f --- /dev/null +++ b/src/jql/tests/jql_test1.c @@ -0,0 +1,306 @@ +#include "jqp.h" +#include "ejdb2_internal.h" +#include +#include +#include +#include + +int init_suite(void) { + int rc = ejdb_init(); + return rc; +} + +int clean_suite(void) { + return 0; +} + +void _jql_test1_1(int num, iwrc expected) { + fprintf(stderr, "%03d.jql\n", num); + + iwrc rc; + char path[64]; + char path_expected[64]; + JQP_AUX *aux; + char *data, *edata = 0; + IWXSTR *res = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(res); + + snprintf(path, sizeof(path), "data%c%03d.jql", IW_PATH_CHR, num); + snprintf(path_expected, sizeof(path_expected), "data%c%03d.expected.jql", IW_PATH_CHR, num); + data = iwu_file_read_as_buf(path); + CU_ASSERT_PTR_NOT_NULL_FATAL(data); + + rc = jqp_aux_create(&aux, data); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jqp_parse(aux); + CU_ASSERT_EQUAL_FATAL(rc, expected); + if (expected) { + goto finish; + } + + CU_ASSERT_PTR_NOT_NULL_FATAL(aux->query); + + edata = iwu_file_read_as_buf(path_expected); + CU_ASSERT_PTR_NOT_NULL_FATAL(edata); + rc = jqp_print_query(aux->query, jbl_xstr_json_printer, res); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + // fprintf(stderr, "%s\n", iwxstr_ptr(res)); + // fprintf(stderr, "%s\n", path_expected); + // fprintf(stderr, "%s\n", edata); + + // fprintf(stderr, "%d\n", strcmp(edata, iwxstr_ptr(res))); + // FILE *out = fopen("out.txt", "w+"); + // fprintf(out, "%s", iwxstr_ptr(res)); + // fclose(out); + + CU_ASSERT_EQUAL_FATAL(strcmp(edata, iwxstr_ptr(res)), 0); + +finish: + if (edata) { + free(edata); + } + free(data); + iwxstr_destroy(res); + jqp_aux_destroy(&aux); +} + +void jql_test1_1() { + + _jql_test1_1(22, 0); + + for (int i = 0; i <= 10; ++i) { + _jql_test1_1(i, 0); + } + for (int i = 11; i <= 13; ++i) { + _jql_test1_1(i, JQL_ERROR_QUERY_PARSE); + } + for (int i = 14; i <= 22; ++i) { + _jql_test1_1(i, 0); + } +} + +static void _jql_test1_2(const char *jsondata, const char *q, bool match) { + JBL jbl; + JQL jql; + char *json = iwu_replace_char(strdup(jsondata), '\'', '"'); + CU_ASSERT_PTR_NOT_NULL_FATAL(json); + iwrc rc = jql_create(&jql, "c1", q); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jbl_from_json(&jbl, json); + CU_ASSERT_EQUAL_FATAL(rc, 0); + bool m = false; + rc = jql_matched(jql, jbl, &m); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_EQUAL_FATAL(m, match); + + jql_destroy(&jql); + jbl_destroy(&jbl); + free(json); +} + +void jql_test1_2() { + _jql_test1_2("{}", "/*", true); + _jql_test1_2("{}", "/**", true); + _jql_test1_2("{'foo':{'bar':22}}", "/*", true); + _jql_test1_2("{'foo':{'bar':22}}", "/**", true); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/bar", true); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/baz", false); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/bar and /foo/bar or /foo", true); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/baz or /foo", true); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/baz and (/foo/daz or /foo/bar)", false); + _jql_test1_2("{'foo':{'bar':22}}", "(/boo or /foo) and (/foo/daz or /foo/bar)", true); + _jql_test1_2("{'foo':{'bar':22, 'bar2':'vvv2'}}", "/foo/bar2", true); + + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[bar = 22]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[bar eq 22]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[bar !eq 22]", false); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[bar != 22]", false); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[bar >= 22]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/*/[bar >= 22]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[bar > 21]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[bar > 22]", false); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[bar < 23]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[bar <= 22]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[bar < 22]", false); + _jql_test1_2("{'foo':{'bar':22}}", "/*/[bar < 22]", false); + _jql_test1_2("{'foo':{'bar':22}}", "/*/[bar > 20 and bar <= 23]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/*/[bar > 22 and bar <= 23]", false); + _jql_test1_2("{'foo':{'bar':22}}", "/*/[bar > 23 or bar < 23]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/*/[bar < 23 or bar > 23]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[[* = bar] = 22]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[[* = bar] != 23]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/[* = foo]/[[* = bar] != 23]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/[* != foo]/[[* = bar] != 23]", false); + + // regexp + _jql_test1_2("{'foo':{'bar':22}}", "/[* re \"foo\"]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/[* re fo]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/[* re ^foo$]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/[* re ^fo$]", false); + _jql_test1_2("{'foo':{'bar':22}}", "/[* not re ^fo$]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[bar re 22]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[bar re \"2+\"]", true); + + // in + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[bar in [21, \"22\"]]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/foo/[bar in [21, 23]]", false); + _jql_test1_2("{'foo':{'bar':22}}", "/[* in [\"foo\"]]/[bar in [21, 22]]", true); + _jql_test1_2("{'foo':{'bar':22}}", "/[* not in [\"foo\"]]/[bar in [21, 22]]", false); + + // Array element + _jql_test1_2("{'tags':['bar', 'foo']}", "/tags/[** in [\"bar\", \"baz\"]]", true); + _jql_test1_2("{'tags':['bar', 'foo']}", "/tags/[** in [\"zaz\", \"gaz\"]]", false); + + // /** + _jql_test1_2("{'foo':{'bar':22}}", "/**", true); + _jql_test1_2("{'foo':{'bar':22}}", "/**/bar", true); + _jql_test1_2("{'foo':{'bar':22}}", "/**/baz", false); + _jql_test1_2("{'foo':{'bar':22}}", "/**/**/bar", true); + _jql_test1_2("{'foo':{'bar':22, 'baz':{'zaz':33}}}", "/foo/**/zaz", true); + _jql_test1_2("{'foo':{'bar':22, 'baz':{'zaz':33}}}", "/foo/**/[zaz > 30]", true); + _jql_test1_2("{'foo':{'bar':22, 'baz':{'zaz':33}}}", "/foo/**/[zaz < 30]", false); + + // arr/obj + _jql_test1_2("{'foo':[1,2]}", "/[foo = [1,2]]", true); + _jql_test1_2("{'foo':[1,2]}", "/[foo ni 2]", true); + _jql_test1_2("{'foo':[1,2]}", "/[foo in [[1,2]]]", true); + _jql_test1_2("{'foo':{'arr':[1,2,3,4]}}", "/foo/[arr = [1,2,3,4]]", true); + _jql_test1_2("{'foo':{'arr':[1,2,3,4]}}", "/foo/**/[arr = [1,2,3,4]]", true); + _jql_test1_2("{'foo':{'arr':[1,2,3,4]}}", "/foo/*/[arr = [1,2,3,4]]", false); + _jql_test1_2("{'foo':{'arr':[1,2,3,4]}}", "/foo/[arr = [1,2,3]]", false); + _jql_test1_2("{'foo':{'arr':[1,2,3,4]}}", "/foo/[arr = [1,12,3,4]]", false); + _jql_test1_2("{'foo':{'obj':{'f':'d','e':'j'}}}", "/foo/[obj = {\"e\":\"j\",\"f\":\"d\"}]", true); + _jql_test1_2("{'foo':{'obj':{'f':'d','e':'j'}}}", "/foo/[obj = {\"e\":\"j\",\"f\":\"dd\"}]", false); + + _jql_test1_2("{'f':22}", "/f", true); + _jql_test1_2("{'a':'bar'}", "/f | asc /f", false); + + // PK + _jql_test1_2("{'f':22}", "/=22", true); + _jql_test1_2("{'f':22}", "@mycoll/=22", true); + + // + const char *doc + = "{" + " 'foo':{" + " 'bar': {'baz':{'zaz':33}}," + " 'sas': {'gaz':{'zaz':44, 'zarr':[42]}}," + " 'arr': [1,2,3,4]" + " }" + "}"; + _jql_test1_2(doc, "/foo/sas/gaz/zaz", true); + _jql_test1_2(doc, "/foo/sas/gaz/[zaz = 44]", true); + _jql_test1_2(doc, "/**/[zaz = 44]", true); + _jql_test1_2(doc, "/foo/**/[zaz = 44]", true); + _jql_test1_2(doc, "/foo/*/*/[zaz = 44]", true); + _jql_test1_2(doc, "/foo/[arr ni 3]", true); + _jql_test1_2(doc, "/**/[zarr ni 42]", true); + _jql_test1_2(doc, "/**/[[* in [\"zarr\"]] in [[42]]]", true); +} + +static void _jql_test1_3(bool has_apply_or_project, const char *jsondata, const char *q, const char *eq) { + JBL jbl; + JQL jql; + JBL_NODE out = 0, eqn = 0; + IWPOOL *pool = iwpool_create(512); + + char *json = iwu_replace_char(strdup(jsondata), '\'', '"'); + CU_ASSERT_PTR_NOT_NULL_FATAL(json); + char *eqjson = iwu_replace_char(strdup(eq), '\'', '"'); + CU_ASSERT_PTR_NOT_NULL_FATAL(eqjson); + char *qstr = iwu_replace_char(strdup(q), '\'', '"'); + CU_ASSERT_PTR_NOT_NULL_FATAL(qstr); + + iwrc rc = jql_create(&jql, "c1", qstr); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jbl_from_json(&jbl, json); + CU_ASSERT_EQUAL_FATAL(rc, 0); + bool m = false; + rc = jql_matched(jql, jbl, &m); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_EQUAL_FATAL(m, true); + + bool hapl = jql_has_apply(jql) || jql_has_projection(jql); + CU_ASSERT_EQUAL_FATAL(hapl, has_apply_or_project); + if (!hapl) { + goto finish; + } + + CU_ASSERT_PTR_NOT_NULL_FATAL(pool); + rc = jql_apply_and_project(jql, jbl, &out, 0, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL_FATAL(out); + + rc = jbn_from_json(eqjson, &eqn, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + int cmp = jbn_compare_nodes(out, eqn, &rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_EQUAL_FATAL(cmp, 0); + +finish: + jql_destroy(&jql); + jbl_destroy(&jbl); + free(json); + free(eqjson); + free(qstr); + iwpool_destroy(pool); +} + +void jql_test1_3() { + + _jql_test1_3(true, "{'foo':{'bar':22}}", + "/foo/bar | apply [{'op':'add', 'path':'/baz', 'value':'qux'}]", + "{'foo':{'bar':22},'baz':'qux'}"); + + _jql_test1_3(true, "{'foo':{'bar':22}}", + "/foo/bar | apply {'baz':'qux'}", + "{'foo':{'bar':22},'baz':'qux'}"); +} + +// Test projections +void jql_test_1_4() { + + _jql_test1_3(false, "{'foo':{'bar':22}}", "/** | all", "{'foo':{'bar':22}}"); + _jql_test1_3(false, "{'foo':{'bar':22}}", "/** | all+all + all", "{'foo':{'bar':22}}"); + _jql_test1_3(true, "{'foo':{'bar':22}}", "/** | all - all", "{}"); + _jql_test1_3(true, "{'foo':{'bar':22}}", "/** | all-all +all", "{}"); + _jql_test1_3(true, "{'foo':{'bar':22}}", "/** | /foo/bar", "{'foo':{'bar':22}}"); + _jql_test1_3(true, "{'foo':{'bar':22, 'baz':'gaz'}}", "/** | /foo/bar", "{'foo':{'bar':22}}"); + _jql_test1_3(true, "{'foo':{'bar':22, 'baz':'gaz'}}", "/** | /foo/{daz,bar}", "{'foo':{'bar':22}}"); + _jql_test1_3(true, "{'foo':{'bar':22, 'baz':{'gaz':444, 'zaz':555}}}", "/** | /foo/bar + /foo/baz/zaz", + "{'foo':{'bar':22, 'baz':{'zaz':555}}}"); + _jql_test1_3(true, "{'foo':{'bar':22, 'baz':{'gaz':444, 'zaz':555}}}", "/** | /foo/bar + /foo/baz/zaz - /*/bar", + "{'foo':{'baz':{'zaz':555}}}"); + _jql_test1_3(true, "{'foo':{'bar':22, 'baz':{'gaz':444, 'zaz':555}}}", "/** | all + /foo/bar + /foo/baz/zaz - /*/bar", + "{'foo':{'baz':{'zaz':555}}}"); + _jql_test1_3(true, "{'foo':{'bar':22}}", "/** | /zzz", "{}"); + _jql_test1_3(true, "{'foo':{'bar':22}}", "/** | /fooo", "{}"); + _jql_test1_3(true, "{'foo':{'bar':22},'name':'test'}", "/** | all - /name", "{'foo':{'bar':22}}"); +} + +int main() { + CU_pSuite pSuite = NULL; + if (CUE_SUCCESS != CU_initialize_registry()) { + return CU_get_error(); + } + pSuite = CU_add_suite("jql_test1", init_suite, clean_suite); + if (NULL == pSuite) { + CU_cleanup_registry(); + return CU_get_error(); + } + if ( (NULL == CU_add_test(pSuite, "jql_test1_1", jql_test1_1)) + || (NULL == CU_add_test(pSuite, "jql_test1_2", jql_test1_2)) + || (NULL == CU_add_test(pSuite, "jql_test1_3", jql_test1_3)) + || (NULL == CU_add_test(pSuite, "jql_test1_4", jql_test_1_4))) { + CU_cleanup_registry(); + return CU_get_error(); + } + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + int ret = CU_get_error() || CU_get_number_of_failures(); + CU_cleanup_registry(); + return ret; +} diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt new file mode 100644 index 0000000..78bc334 --- /dev/null +++ b/src/tests/CMakeLists.txt @@ -0,0 +1,19 @@ +link_libraries(ejdb2_s ${CUNIT_LIBRARIES}) +include_directories(${CUNIT_INCLUDE_DIRS}) + +set(TEST_DATA_DIR ${CMAKE_CURRENT_BINARY_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${TEST_DATA_DIR}) + +set(TESTS ejdb_test1 + ejdb_test2 + ejdb_test3 + ejdb_test4 + ) + +foreach (TN IN ITEMS ${TESTS}) + add_executable(${TN} ${TN}.c) + set_target_properties(${TN} PROPERTIES + COMPILE_FLAGS "-DIW_STATIC") + add_test(NAME ${TN} WORKING_DIRECTORY ${TEST_DATA_DIR} + COMMAND ${TEST_TOOL_CMD} $) +endforeach () diff --git a/src/tests/ejdb_test.h b/src/tests/ejdb_test.h new file mode 100644 index 0000000..3c48682 --- /dev/null +++ b/src/tests/ejdb_test.h @@ -0,0 +1,90 @@ +#pragma once +#ifndef EJDB_TEST_H +#define EJDB_TEST_H + +/************************************************************************************************** + * EJDB2 + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +#include "ejdb2.h" +#include "ejdb2_internal.h" +#include + +static iwrc put_json(EJDB db, const char *coll, const char *json) { + const size_t len = strlen(json); + char buf[len + 1]; + memcpy(buf, json, len); + buf[len] = '\0'; + for (int i = 0; buf[i]; ++i) { + if (buf[i] == '\'') { + buf[i] = '"'; + } + } + JBL jbl; + int64_t llv; + iwrc rc = jbl_from_json(&jbl, buf); + RCRET(rc); + rc = ejdb_put_new(db, coll, jbl, &llv); + jbl_destroy(&jbl); + return rc; +} + +static iwrc put_json2(EJDB db, const char *coll, const char *json, int64_t *id) { + const size_t len = strlen(json); + char buf[len + 1]; + memcpy(buf, json, len); + buf[len] = '\0'; + for (int i = 0; buf[i]; ++i) { + if (buf[i] == '\'') { + buf[i] = '"'; + } + } + JBL jbl; + iwrc rc = jbl_from_json(&jbl, buf); + RCRET(rc); + + if (*id == 0) { + rc = ejdb_put_new(db, coll, jbl, id); + } else { + rc = ejdb_put(db, coll, jbl, *id); + } + jbl_destroy(&jbl); + return rc; +} + +static iwrc patch_json(EJDB db, const char *coll, const char *patchjson, int64_t id) { + const size_t len = strlen(patchjson); + char buf[len + 1]; + memcpy(buf, patchjson, len); + buf[len] = '\0'; + for (int i = 0; buf[i]; ++i) { + if (buf[i] == '\'') { + buf[i] = '"'; + } + } + return ejdb_patch(db, coll, buf, id); +} + +#endif diff --git a/src/tests/ejdb_test1.c b/src/tests/ejdb_test1.c new file mode 100644 index 0000000..e2e3580 --- /dev/null +++ b/src/tests/ejdb_test1.c @@ -0,0 +1,316 @@ +#include "ejdb2.h" +#include "ejdb_test.h" +#include +#include + +int init_suite() { + int rc = ejdb_init(); + return rc; +} + +int clean_suite() { + return 0; +} + +void ejdb_test1_3() { + EJDB_OPTS opts = { + .kv = { + .path = "ejdb_test1_3.db", + .oflags = IWKV_TRUNC + }, + .no_wal = true + }; + EJDB db; + JBL jbl; + int64_t id = 0; + + iwrc rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = put_json2(db, "c1", "{'foo':'bar'}", &id); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_TRUE_FATAL(id > 0); + + rc = patch_json(db, "c1", "[ { 'op': 'add', 'path': '/baz', 'value': 'qux' } ]", id); + if (rc) { + iwlog_ecode_error3(rc); + } + CU_ASSERT_EQUAL_FATAL(rc, 0); + + // Now check the result + rc = ejdb_get(db, "c1", id, &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + IWXSTR *xstr = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(xstr); + + rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"foo\":\"bar\",\"baz\":\"qux\"}"); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_destroy(xstr); + jbl_destroy(&jbl); +} + +void ejdb_test1_2() { + EJDB_OPTS opts = { + .kv = { + .path = "ejdb_test1_2.db", + .oflags = IWKV_TRUNC + }, + .no_wal = true + }; + EJDB db; + JBL jbl, at, meta; + int64_t llv = 0, llv2; + iwrc rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbl_from_json(&jbl, "{\"foo\":22}"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = ejdb_put_new(db, "foocoll", jbl, &llv); + CU_ASSERT_EQUAL_FATAL(rc, 0); + jbl_destroy(&jbl); + CU_ASSERT_TRUE(llv > 0); + + rc = ejdb_get(db, "foocoll", llv, &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbl_at(jbl, "/foo", &at); + CU_ASSERT_EQUAL_FATAL(rc, 0); + llv2 = jbl_get_i64(at); + CU_ASSERT_EQUAL(llv2, 22); + jbl_destroy(&at); + jbl_destroy(&jbl); + + rc = ejdb_del(db, "foocoll", llv); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_get(db, "foocoll", llv, &jbl); + CU_ASSERT_EQUAL(rc, IWKV_ERROR_NOTFOUND); + CU_ASSERT_PTR_NULL(jbl); + + rc = jbl_from_json(&jbl, "{\"foo\":22}"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = ejdb_put_new(db, "foocoll", jbl, &llv); + CU_ASSERT_EQUAL_FATAL(rc, 0); + jbl_destroy(&jbl); + CU_ASSERT_TRUE(llv > 0); + + // Ensure indexes + rc = ejdb_ensure_index(db, "col1", "/foo/bar", EJDB_IDX_I64 | EJDB_IDX_UNIQUE); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_ensure_index(db, "col1", "/foo/baz", EJDB_IDX_STR); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_ensure_index(db, "col1", "/foo/gaz", EJDB_IDX_STR | EJDB_IDX_UNIQUE); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_get_meta(db, &meta); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + // fprintf(stderr, "\n"); + // rc = jbl_as_json(meta, jbl_fstream_json_printer, stderr, JBL_PRINT_PRETTY); + // CU_ASSERT_EQUAL_FATAL(rc, 0); + // fprintf(stderr, "\n"); + + // "version": "2.0.0", + // "file": "ejdb_test1_2.db", + // "size": 8192, + // "collections": [ + // { + // "name": "col1", + // "dbid": 3, + // "indexes": [ + // { + // "ptr": "/foo/bar", + // "mode": 9, + // "idbf": 2, + // "dbid": 4, + // "auxdbid": 0 + // }, + // { + // "ptr": "/foo/baz", + // "mode": 4, + // "idbf": 8, + // "dbid": 5, + // "auxdbid": 0 + // } + // ] + // }, + // { + // "name": "foocoll", + // "dbid": 2, + // "indexes": [] + // } + // ] + + rc = jbl_at(meta, "/collections/0/name", &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(jbl_get_str(jbl), "col1"); + jbl_destroy(&jbl); + + rc = jbl_at(meta, "/collections/0/indexes/0/ptr", &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(jbl_get_str(jbl), "/foo/gaz"); + jbl_destroy(&jbl); + + rc = jbl_at(meta, "/collections/1/name", &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(jbl_get_str(jbl), "foocoll"); + jbl_destroy(&jbl); + + rc = jbl_at(meta, "/collections/0/indexes/1/ptr", &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(jbl_get_str(jbl), "/foo/baz"); + jbl_destroy(&jbl); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + jbl_destroy(&meta); + + // Now reopen db and check indexes + opts.kv.oflags &= ~IWKV_TRUNC; + rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_get_meta(db, &meta); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbl_at(meta, "/collections/0/name", &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(jbl_get_str(jbl), "col1"); + jbl_destroy(&jbl); + + rc = jbl_at(meta, "/collections/0/indexes/0/ptr", &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(jbl_get_str(jbl), "/foo/gaz"); + jbl_destroy(&jbl); + + rc = jbl_at(meta, "/collections/1/name", &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(jbl_get_str(jbl), "foocoll"); + jbl_destroy(&jbl); + + rc = jbl_at(meta, "/collections/1/rnum", &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_EQUAL(jbl_get_i64(jbl), 1); + jbl_destroy(&jbl); + + rc = jbl_at(meta, "/collections/0/indexes/1/ptr", &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(jbl_get_str(jbl), "/foo/baz"); + jbl_destroy(&jbl); + jbl_destroy(&meta); + + rc = ejdb_remove_index(db, "col1", "/foo/baz", EJDB_IDX_STR); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_remove_index(db, "col1", "/foo/gaz", EJDB_IDX_STR); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_get_meta(db, &meta); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbl_at(meta, "/collections/1/indexes/0/ptr", &jbl); + CU_ASSERT_EQUAL_FATAL(rc, JBL_ERROR_PATH_NOTFOUND); + + rc = jbl_at(meta, "/collections/2/indexes/0/ptr", &jbl); + CU_ASSERT_EQUAL_FATAL(rc, JBL_ERROR_PATH_NOTFOUND); + + rc = jbl_at(meta, "/collections/0/indexes/0/ptr", &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(jbl_get_str(jbl), "/foo/bar"); + jbl_destroy(&jbl); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + jbl_destroy(&meta); +} + +void ejdb_test1_1() { + EJDB_OPTS opts = { + .kv = { + .path = "ejdb_test1_1.db", + .oflags = IWKV_TRUNC + }, + .no_wal = true + }; + EJDB db; + JBL meta, jbl; + iwrc rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = ejdb_ensure_collection(db, "foo"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + // Meta: { + // "version": "2.0.0", + // "file": "ejdb_test1_1.db", + // "size": 8192, + // "collections": [ + // { + // "name": "foo", + // "dbid": 2 + // } + // ] + //} + rc = ejdb_get_meta(db, &meta); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbl_at(meta, "/file", &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(jbl_get_str(jbl), "ejdb_test1_1.db"); + jbl_destroy(&jbl); + + rc = jbl_at(meta, "/collections/0/name", &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(jbl_get_str(jbl), "foo"); + jbl_destroy(&jbl); + jbl_destroy(&meta); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + // Now reopen database then load collection + opts.kv.oflags &= ~IWKV_TRUNC; + rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = ejdb_remove_collection(db, "foo"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_get_meta(db, &meta); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbl_at(meta, "/collections/0/name", &jbl); // No collections + CU_ASSERT_EQUAL_FATAL(rc, JBL_ERROR_PATH_NOTFOUND); + jbl_destroy(&meta); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); +} + +int main() { + CU_pSuite pSuite = NULL; + if (CUE_SUCCESS != CU_initialize_registry()) { + return CU_get_error(); + } + pSuite = CU_add_suite("ejdb_test1", init_suite, clean_suite); + if (NULL == pSuite) { + CU_cleanup_registry(); + return CU_get_error(); + } + if ( (NULL == CU_add_test(pSuite, "ejdb_test1_1", ejdb_test1_1)) + || (NULL == CU_add_test(pSuite, "ejdb_test1_2", ejdb_test1_2)) + || (NULL == CU_add_test(pSuite, "ejdb_test1_3", ejdb_test1_3))) { + CU_cleanup_registry(); + return CU_get_error(); + } + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + int ret = CU_get_error() || CU_get_number_of_failures(); + CU_cleanup_registry(); + return ret; +} diff --git a/src/tests/ejdb_test2.c b/src/tests/ejdb_test2.c new file mode 100644 index 0000000..e616428 --- /dev/null +++ b/src/tests/ejdb_test2.c @@ -0,0 +1,321 @@ +#include "ejdb_test.h" +#include +#include + +int init_suite() { + int rc = ejdb_init(); + return rc; +} + +int clean_suite() { + return 0; +} + +// Test document sorting overflow on disk +static void ejdb_test2_2() { + EJDB_OPTS opts = { + .kv = { + .path = "ejdb_test2_2.db", + .oflags = IWKV_TRUNC + }, + .document_buffer_sz = 16 * 1024, // 16K + .sort_buffer_sz = 1024 * 1024, // 1M + .no_wal = true + }; + EJDB db; + EJDB_LIST list = 0; + const int vbufsz = 512 * 1024; + const int dbufsz = vbufsz + 128; + char *vbuf = malloc(vbufsz); + char *dbuf = malloc(dbufsz); + CU_ASSERT_PTR_NOT_NULL_FATAL(vbuf); + CU_ASSERT_PTR_NOT_NULL_FATAL(dbuf); + memset(vbuf, 'z', vbufsz); + vbuf[vbufsz - 1] = '\0'; + + iwrc rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + for (int i = 0; i < 6; ++i) { + snprintf(dbuf, dbufsz, "{\"f\":%d, \"d\":\"%s\"}", i, vbuf); + rc = put_json(db, "c1", dbuf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + } + // Here is we will overflow sort buffer + rc = ejdb_list2(db, "c1", "/f | asc /f", 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + int i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + JBL jbl; + rc = jbl_at(doc->raw, "/f", &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + int64_t llv = jbl_get_i64(jbl); + jbl_destroy(&jbl); + CU_ASSERT_EQUAL(llv, i); + } + ejdb_list_destroy(&list); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + free(vbuf); + free(dbuf); +} + +struct TEST21_1 { + int stage; + int cnt; + IWXSTR *xstr; +}; + +static iwrc ejdb_test2_1_exec_visitor1(struct _EJDB_EXEC *ctx, const EJDB_DOC doc, int64_t *step) { + struct TEST21_1 *tc = ctx->opaque; + JBL jbl; + iwrc rc = jbl_at(doc->raw, "/f", &jbl); + RCRET(rc); + int64_t llv = jbl_get_i64(jbl); + if (tc->cnt && (tc->stage == 0)) { + tc->stage = 1; + *step = 2; + } else if (tc->stage == 1) { + tc->stage = 2; + *step = -1; + } + jbl_destroy(&jbl); + iwxstr_printf(tc->xstr, "%lld", llv); + tc->cnt++; + return rc; +} + +static void ejdb_test2_1() { + EJDB_OPTS opts = { + .kv = { + .path = "ejdb_test2_1.db", + .oflags = IWKV_TRUNC + }, + .no_wal = true + }; + + EJDB db; + EJDB_LIST list = 0; + IWXSTR *xstr = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(xstr); + int i = 0; + + iwrc rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = put_json(db, "a", "{'f':2}"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = put_json(db, "a", "{'f':1}"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = put_json(db, "a", "{'f':3}"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = put_json(db, "a", "{'a':'foo'}"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = put_json(db, "a", "{'a':'gaz'}"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = put_json(db, "a", "{'a':'bar'}"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_list2(db, "not_exists", "/*", 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + } + CU_ASSERT_EQUAL(i, 0); + ejdb_list_destroy(&list); + + rc = ejdb_list2(db, "a", "/*", 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + switch (i) { + case 0: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"a\":\"bar\"}"); + break; + case 1: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"a\":\"gaz\"}"); + break; + case 2: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"a\":\"foo\"}"); + break; + case 5: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":2}"); + break; + } + } + CU_ASSERT_EQUAL(i, 6); + ejdb_list_destroy(&list); + + rc = ejdb_list2(db, "a", "/*", 1, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + } + CU_ASSERT_EQUAL(i, 1); + ejdb_list_destroy(&list); + + rc = ejdb_list2(db, "a", "/f", 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + } + CU_ASSERT_EQUAL(i, 3); + ejdb_list_destroy(&list); + + rc = ejdb_list2(db, "a", "/* | skip 1", 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + switch (i) { + case 0: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"a\":\"gaz\"}"); + break; + } + } + CU_ASSERT_EQUAL(i, 5); + ejdb_list_destroy(&list); + + rc = ejdb_list2(db, "a", "/* | skip 2 limit 3", 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + switch (i) { + case 0: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"a\":\"foo\"}"); + break; + } + } + CU_ASSERT_EQUAL(i, 3); + ejdb_list_destroy(&list); + + // Add {f:5}, {f:6} + rc = put_json(db, "a", "{'f':5}"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = put_json(db, "a", "{'f':6}"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_list2(db, "a", "/f | asc /f", 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + switch (i) { + case 0: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":1}"); + break; + case 1: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":2}"); + break; + case 2: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":3}"); + break; + case 3: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":5}"); + break; + case 4: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":6}"); + break; + } + } + CU_ASSERT_EQUAL(i, 5); + ejdb_list_destroy(&list); + + rc = ejdb_list2(db, "a", "/f | desc /f", 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + switch (i) { + case 0: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":6}"); + break; + case 1: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":5}"); + break; + case 2: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":3}"); + break; + case 3: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":2}"); + break; + case 4: + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":1}"); + break; + } + } + CU_ASSERT_EQUAL(i, 5); + ejdb_list_destroy(&list); + + // + // Now test basic back/forward skips + // + JQL q; + struct TEST21_1 tc = { 0 }; + tc.xstr = iwxstr_new(); + rc = jql_create(&q, "a", "/f"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + EJDB_EXEC ux = { + .db = db, + .q = q, + .opaque = &tc, + .visitor = ejdb_test2_1_exec_visitor1 + }; + rc = ejdb_exec(&ux); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(tc.xstr), "65112"); + jql_destroy(&q); + iwxstr_destroy(tc.xstr); + + // back/forward skips for sorted output + memset(&tc, 0, sizeof(tc)); + tc.xstr = iwxstr_new(); + rc = jql_create(&q, "a", "/f | asc /f"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + ux.q = q; + rc = ejdb_exec(&ux); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(tc.xstr), "12556"); + jql_destroy(&q); + iwxstr_destroy(tc.xstr); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_destroy(xstr); +} + +int main() { + CU_pSuite pSuite = NULL; + if (CUE_SUCCESS != CU_initialize_registry()) { + return CU_get_error(); + } + pSuite = CU_add_suite("ejdb_test1", init_suite, clean_suite); + if (NULL == pSuite) { + CU_cleanup_registry(); + return CU_get_error(); + } + if ( (NULL == CU_add_test(pSuite, "ejdb_test2_1", ejdb_test2_1)) + || (NULL == CU_add_test(pSuite, "ejdb_test2_2", ejdb_test2_2))) { + CU_cleanup_registry(); + return CU_get_error(); + } + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + int ret = CU_get_error() || CU_get_number_of_failures(); + CU_cleanup_registry(); + return ret; +} diff --git a/src/tests/ejdb_test3.c b/src/tests/ejdb_test3.c new file mode 100644 index 0000000..f93f42e --- /dev/null +++ b/src/tests/ejdb_test3.c @@ -0,0 +1,1148 @@ +#include "ejdb_test.h" +#include + +int init_suite() { + int rc = ejdb_init(); + return rc; +} + +int clean_suite() { + return 0; +} + +static void ejdb_test3_1() { + EJDB_OPTS opts = { + .kv = { + .path = "ejdb_test3_1.db", + .oflags = IWKV_TRUNC + }, + .no_wal = true + }; + + EJDB db; + char dbuf[1024]; + EJDB_LIST list = 0; + IWXSTR *log = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(log); + IWXSTR *xstr = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(xstr); + + iwrc rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_ensure_index(db, "c1", "/f/b", EJDB_IDX_UNIQUE | EJDB_IDX_I64); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + for (int i = 1; i <= 10; ++i) { + snprintf(dbuf, sizeof(dbuf), "{\"f\":{\"b\":%d},\"n\":%d}", i, i); + rc = put_json(db, "c1", dbuf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + if (i == 1) { // Check unique index constraint violation + rc = put_json(db, "c1", dbuf); + CU_ASSERT_EQUAL(rc, EJDB_ERROR_UNIQUE_INDEX_CONSTRAINT_VIOLATED); + } + rc = put_json(db, "c2", dbuf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + } + + rc = ejdb_ensure_index(db, "c2", "/f/b", EJDB_IDX_UNIQUE | EJDB_IDX_I64); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_list3(db, "c1", "/f/[b = 1]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + CU_ASSERT_PTR_NOT_NULL_FATAL(strstr(iwxstr_ptr(log), "[INDEX] MATCHED UNIQUE|I64|10 /f/b EXPR1: 'b = 1' " + "INIT: IWKV_CURSOR_EQ")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "[INDEX] SELECTED UNIQUE|I64|10 /f/b EXPR1: 'b = 1' " + "INIT: IWKV_CURSOR_EQ")); + int i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":1},\"n\":1}"); + } + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Q: /f/[b > 1] + rc = ejdb_list3(db, "c1", "/f/[b > 1]", 0, log, &list); + //if (rc) iwlog_ecode_error3(rc); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "MATCHED UNIQUE|I64|10 /f/b EXPR1: 'b > 1' " + "INIT: IWKV_CURSOR_GE")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "SELECTED UNIQUE|I64|10 /f/b EXPR1: 'b > 1' " + "INIT: IWKV_CURSOR_GE")); + + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + if (i == 0) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":2},\"n\":2}"); + } else if (i == 8) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":10},\"n\":10}"); + } + } + CU_ASSERT_EQUAL(i, 9); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Q: /f/[b >= 2] + rc = ejdb_list3(db, "c1", "/f/[b >= 3]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + if (i == 0) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":3},\"n\":3}"); + } else if (i == 8) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":10},\"n\":10}"); + } + } + CU_ASSERT_EQUAL(i, 8); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Q: /f/[b < 9] + rc = ejdb_list3(db, "c1", "/f/[b < 9]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + if (i == 0) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":8},\"n\":8}"); + } else if (i == 7) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":1},\"n\":1}"); + } + } + CU_ASSERT_EQUAL(i, 8); + ejdb_list_destroy(&list); + + // Q: /f/[b < 11] + rc = ejdb_list3(db, "c1", "/f/[b < 11]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + if (i == 0) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":10},\"n\":10}"); + } else if (i == 9) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":1},\"n\":1}"); + } + } + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Q: /f/[b < 11 and b >= 4] + rc = ejdb_list3(db, "c1", "/f/[b < 11 and b >= 4]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "[INDEX] SELECTED UNIQUE|I64|10 /f/b EXPR1: 'b >= 4' EXPR2: 'b < 11' " + "INIT: IWKV_CURSOR_GE")); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + if (i == 0) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":4},\"n\":4}"); + } else if (i == 6) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":10},\"n\":10}"); + } + } + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Q: /f/[b < 11 and b > 0 or b = 0] + rc = ejdb_list3(db, "c1", "/f/[b < 11 and b > 0 or b = 0]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Q: /f/[b > 1 and b < 2 and b = 2 and b < 3] + rc = ejdb_list3(db, "c1", "/f/[b > 1 and b < 2 and b = 2 and b < 3]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), + "[INDEX] SELECTED UNIQUE|I64|10 /f/b EXPR1: 'b = 2' " + "INIT: IWKV_CURSOR_EQ")); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Q: /f/[b > 1 and b < 3 and b <= 4] + rc = ejdb_list3(db, "c1", "/f/[b > 1 and b < 3 and b <= 4]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), + "[INDEX] SELECTED UNIQUE|I64|10 /f/b EXPR1: 'b > 1' EXPR2: 'b < 3' " + "INIT: IWKV_CURSOR_GE")); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":2},\"n\":2}"); + } + CU_ASSERT_EQUAL(i, 1); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Q: /f/[b > 1] and /f/[b < 3] + rc = ejdb_list3(db, "c1", "/f/[b > 1] and /f/[b < 3]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), + "[INDEX] MATCHED UNIQUE|I64|10 /f/b EXPR1: 'b > 1' INIT: IWKV_CURSOR_GE")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), + "[INDEX] MATCHED UNIQUE|I64|10 /f/b EXPR1: 'b < 3' INIT: IWKV_CURSOR_GE")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), + "[INDEX] SELECTED UNIQUE|I64|10 /f/b EXPR1: 'b > 1' INIT: IWKV_CURSOR_GE")); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Q: /f/[b > 1 or b != 1] and /f/[b < 3] + rc = ejdb_list3(db, "c1", "/f/[b > 1 or b != 1] and /f/[b < 3]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), + "[INDEX] SELECTED UNIQUE|I64|10 /f/b EXPR1: 'b < 3' " + "INIT: IWKV_CURSOR_GE")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "[COLLECTOR] PLAIN")); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":2},\"n\":2}"); + } + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Q: /f/[b in [2,1112,4,6]] + rc = ejdb_list3(db, "c1", "/f/[b in [2,1112,4,6]]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), + "[INDEX] SELECTED UNIQUE|I64|10 /f/b EXPR1: 'b in [2,1112,4,6]' " + "INIT: IWKV_CURSOR_EQ")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "[COLLECTOR] PLAIN")); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + if (i == 0) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":2},\"n\":2}"); + } else if (i == 1) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":4},\"n\":4}"); + } else if (i == 2) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":6},\"n\":6}"); + } + } + CU_ASSERT_EQUAL(i, 3); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Q: /f/[b > 1] | asc /f/b + rc = ejdb_list3(db, "c1", "/f/[b > 1] | asc /f/b", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), + "[INDEX] SELECTED UNIQUE|I64|10 /f/b EXPR1: 'b > 1' " + "INIT: IWKV_CURSOR_GE STEP: IWKV_CURSOR_PREV ORDERBY")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "[COLLECTOR] PLAIN")); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + if (i == 0) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":2},\"n\":2}"); + } else if (i == 8) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":10},\"n\":10}"); + } + } + CU_ASSERT_EQUAL(i, 9); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Q: /f/[b > 1] | desc /f/b + rc = ejdb_list3(db, "c1", "/f/[b > 1] | desc /f/b", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), + "[INDEX] SELECTED UNIQUE|I64|10 /f/b EXPR1: 'b > 1' " + "INIT: IWKV_CURSOR_GE")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "[COLLECTOR] SORTER")); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + if (i == 0) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":10},\"n\":10}"); + } else if (i == 8) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":2},\"n\":2}"); + } + } + CU_ASSERT_EQUAL(i, 9); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Q: /f/[b > 2 and b < 10] | asc /f/b + rc = ejdb_list3(db, "c1", "/f/[b > 2 and b < 10] | asc /f/b", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), + "[INDEX] SELECTED UNIQUE|I64|10 /f/b EXPR1: 'b > 2' EXPR2: 'b < 10' " + "INIT: IWKV_CURSOR_GE STEP: IWKV_CURSOR_PREV ORDERBY")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "[COLLECTOR] PLAIN")); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + if (i == 0) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":3},\"n\":3}"); + } else if (i == 6) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":9},\"n\":9}"); + } + } + CU_ASSERT_EQUAL(i, 7); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Q: /f/[b > 2 and b < 10] | desc /f/b + rc = ejdb_list3(db, "c1", "/f/[b > 2 and b < 10] | desc /f/b", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "[INDEX] SELECTED UNIQUE|I64|10 /f/b EXPR1: 'b < 10' EXPR2: 'b > 2' " + "INIT: IWKV_CURSOR_GE STEP: IWKV_CURSOR_NEXT ORDERBY")); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "[COLLECTOR] PLAIN")); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + if (i == 0) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":9},\"n\":9}"); + } else if (i == 6) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":3},\"n\":3}"); + } + } + CU_ASSERT_EQUAL(i, 7); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_destroy(log); + iwxstr_destroy(xstr); +} + +static void ejdb_test3_2() { + EJDB_OPTS opts = { + .kv = { + .path = "ejdb_test3_2.db", + .oflags = IWKV_TRUNC + }, + .no_wal = true + }; + + EJDB db; + char dbuf[1024]; + EJDB_LIST list = 0; + int i = 0; + IWXSTR *log = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(log); + IWXSTR *xstr = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(xstr); + + iwrc rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_ensure_index(db, "a1", "/f/b", EJDB_IDX_I64); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + // Data: + // + // {"f":{"b":16777215},"n":9} + // {"f":{"b":16777215},"n":7} + // {"f":{"b":16777215},"n":5} + // {"f":{"b":16777215},"n":3} + // {"f":{"b":16777215},"n":1} + // {"f":{"b":127},"n":10} + // {"f":{"b":127},"n":8} + // {"f":{"b":127},"n":6} + // {"f":{"b":127},"n":4} + // {"f":{"b":127},"n":2} + // {"f":{"b":126},"n":12} + // {"f":{"b":126},"n":11} + // + + for (i = 1; i <= 10; ++i) { + int v = (i % 2) ? 0xffffff : 127; + //fprintf(stderr, "\n{\"f\":{\"b\":%d},\"n\":%d}", v, i); + snprintf(dbuf, sizeof(dbuf), "{\"f\":{\"b\":%d},\"n\":%d}", v, i); + rc = put_json(db, "a1", dbuf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + } + snprintf(dbuf, sizeof(dbuf), "{\"f\":{\"b\":%d},\"n\":%d}", 126, 11); + rc = put_json(db, "a1", dbuf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + snprintf(dbuf, sizeof(dbuf), "{\"f\":{\"b\":%d},\"n\":%d}", 126, 12); + rc = put_json(db, "a1", dbuf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + // GT + rc = ejdb_list3(db, "a1", "/f/[b > 127]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "[INDEX] SELECTED I64|12 /f/b EXPR1: 'b > 127' " + "INIT: IWKV_CURSOR_GE STEP: IWKV_CURSOR_PREV")); + + i = 1; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + if (i == 1) { + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":16777215},\"n\":1}"); + } else if (i == 9) { + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":16777215},\"n\":9}"); + } + } + CU_ASSERT_EQUAL(i, 6); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // LT + rc = ejdb_list3(db, "a1", "/f/[b < 127]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "SELECTED I64|12 /f/b EXPR1: 'b < 127' " + "INIT: IWKV_CURSOR_GE STEP: IWKV_CURSOR_NEXT")); + + i = 1; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + if (i == 1) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":126},\"n\":12}"); + } else if (i == 2) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":126},\"n\":11}"); + } + } + CU_ASSERT_EQUAL(i, 3); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // LT2 + rc = ejdb_list3(db, "a1", "/f/[b < 16777216]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + i = 1; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + if (i == 1) { + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":16777215},\"n\":9}"); + } else if (i == 12) { + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":126},\"n\":11}"); + } + } + CU_ASSERT_EQUAL(i, 13); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // EQ + rc = ejdb_list3(db, "a1", "/f/[b = 127]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "SELECTED I64|12 /f/b EXPR1: 'b = 127' " + "INIT: IWKV_CURSOR_EQ")); + + i = 1; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + if (i == 1) { + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":127},\"n\":2}"); + } else if (i == 5) { + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":127},\"n\":10}"); + } + } + CU_ASSERT_EQUAL(i, 6); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // IN + rc = ejdb_list3(db, "a1", "/f/[b in [333, 16777215, 127, 16777216]]", 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "[INDEX] SELECTED I64|12 /f/b " + "EXPR1: 'b in [333,16777215,127,16777216]' " + "INIT: IWKV_CURSOR_EQ")); + i = 1; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + if (i == 1) { + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":127},\"n\":2}"); + } else if (i == 5) { + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":127},\"n\":10}"); + } else if (i == 6) { + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":16777215},\"n\":1}"); + } else if (i == 10) { + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":16777215},\"n\":9}"); + } + } + CU_ASSERT_EQUAL(i, 11); + + ejdb_list_destroy(&list); + iwxstr_clear(log); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_destroy(log); + iwxstr_destroy(xstr); +} + +static void ejdb_test3_3() { + EJDB_OPTS opts = { + .kv = { + .path = "ejdb_test3_3.db", + .oflags = IWKV_TRUNC + }, + .no_wal = true + }; + EJDB db; + char dbuf[1024]; + + int i = 0; + EJDB_LIST list = 0; + IWXSTR *log = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(log); + IWXSTR *xstr = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(xstr); + + iwrc rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_ensure_index(db, "a2", "/f/b", EJDB_IDX_STR); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + char *data[] = { + + // len: 200 + "Ar4prlJssa2ckf0IpmDuRBZ2b0Q6PtPdTacjWFFuO23CiCjdyfHaliz9JaqK1HFEeaneiMO" + "7sNh87oDLVkvz7TnOV22v0njqmmd6b8DSfzaCwxFxcqrF7MinjUvJvct1Fr07MJWeG7C6SP" + "MlUjFQ4jNlds3kUQDP9yxQImH7BkmCqBCisIoh5zar8zSax1Pk7nZSpm1b", + + // len: 115 + "BkMSITU2qJ56xeX3nftUd3g4PuZwo9LY2mTGFtYKrrqhilPiR0UTHrDobstoShELlMHvPx61" + "KF8qQRPAn4OOUttNtkPE95XsjZQ8PPZW9ruWo1R9UMx", + + // len: 64 + "C1257xkZuJqXhQ5v5eWG8TlwKdCY77DQ0ScLyC3nGDTtC3A8DPDAiVC09EBFTUxp", + + // len: 800 + "D9z1bYv2oEp8n8B0BtY1VI4ezy8adAnPvqno9rdxM7RsMZDcLQyCEJ3vDMFqoJaRNbCtdbHh09" + "L0UijAR6wmQ87P90eAGKaEvuhoRzhoDZYpa37o7HZrBctcCxGHSrQMR0o1NKOz2vmEvhX6k02M" + "QopatRrL6jIkr9XXKgekOh6xcVyvUcnr6ttD3tqF0v0QN3ZPnXRCcVYyx0Ot9T6EfZik7HO3QW" + "jyblg4f4qqohprmGWKO8UfIIsF1gRPPacPc8oJXEFrbJu5NR4LidKpn6ygmTpstGVCanpCq2Yi" + "pkWrQpC1LkdSh3h2hNMUZbgZDgsvGzocuBuGgnyDd6I91PJjBmbFTXmILkT9t0ApvCGJTrc9aB" + "Sw5I9CAZRFRqVnRFUr7fA0OfQhN2zCu4d5m1XQg3yh9We4GI1ffhWsbnH4g59HbuAkm9jmz4mp" + "B6IJUSewlgq6YDgfsPNQXHboBVWAuR9NONIpfpmnU4wjuwI3j3QJAbi81u23QXAWJETvVBRqqU" + "ZqtqUjwnOCoPvkRV3WhfEHezmN9HTuxxl75WowRXyz8nwUe3xOXadmQwkhyYbWSSrO835XziTj" + "58e00zGi6eLwXD7xrKC7YeEb7HE4L8eKeEFiM1xC00ySIDkBNoclMxmExPg6kBUpiUT7HAEfaN" + "AN2VbdWFZsumDQHu3Q5XwrGdiQ6ubLTMuQEIv1IPTZIRTV5TQ59aUdiM6POdLFv9xuDjEgnBYd" + "MxcP60sNDIakuW2IeabKScwF5yx9PAg7D9K5WVWhpSQuzDFiTSSJPwkQfoWo" + }; + + snprintf(dbuf, sizeof(dbuf), "{\"f\":{\"b\":\"%s\"},\"n\":%d}", data[0], 1); + rc = put_json(db, "a2", dbuf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + snprintf(dbuf, sizeof(dbuf), "{\"f\":{\"b\":\"%s\"},\"n\":%d}", data[1], 2); + rc = put_json(db, "a2", dbuf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + snprintf(dbuf, sizeof(dbuf), "{\"f\":{\"b\":\"%s\"},\"n\":%" PRId64 "}", data[1], INT64_MAX - 1); + rc = put_json(db, "a2", dbuf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + snprintf(dbuf, sizeof(dbuf), "{\"f\":{\"b\":\"%s\"},\"n\":%d}", data[2], 3); + rc = put_json(db, "a2", dbuf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + snprintf(dbuf, sizeof(dbuf), "{\"f\":{\"b\":\"%s\"},\"n\":%d}", data[2], 4); + rc = put_json(db, "a2", dbuf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + + snprintf(dbuf, sizeof(dbuf), "{\"f\":{\"b\":\"%s\"},\"n\":%d}", data[3], 5); + rc = put_json(db, "a2", dbuf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + // Q: /f/[b >= data[0]] + JQL q; + rc = jql_create(&q, "a2", "/f/[b >= :?]"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jql_set_str(q, 0, 0, data[0]); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_list4(db, q, 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "[INDEX] SELECTED STR|6 /f/b EXPR1: 'b >= :?' " + "INIT: IWKV_CURSOR_GE STEP: IWKV_CURSOR_PREV")); + i = 1; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + JBL jbl1, jbl2; + rc = jbl_at(doc->raw, "/f/b", &jbl1); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jbl_at(doc->raw, "/n", &jbl2); + CU_ASSERT_EQUAL_FATAL(rc, 0); + int64_t llv = jbl_get_i64(jbl2); + const char *str = jbl_get_str(jbl1); + switch (i) { + case 1: + CU_ASSERT_EQUAL(llv, 1); + CU_ASSERT_STRING_EQUAL(str, data[0]); + break; + case 2: + CU_ASSERT_EQUAL(llv, 2); + CU_ASSERT_STRING_EQUAL(str, data[1]); + break; + case 3: + CU_ASSERT_EQUAL(llv, INT64_MAX - 1); + CU_ASSERT_STRING_EQUAL(str, data[1]); + break; + case 4: + CU_ASSERT_EQUAL(llv, 3); + CU_ASSERT_STRING_EQUAL(str, data[2]); + break; + case 5: + CU_ASSERT_EQUAL(llv, 4); + CU_ASSERT_STRING_EQUAL(str, data[2]); + break; + case 6: + CU_ASSERT_EQUAL(llv, 5); + CU_ASSERT_STRING_EQUAL(str, data[3]); + break; + } + jbl_destroy(&jbl1); + jbl_destroy(&jbl2); + } + CU_ASSERT_EQUAL(i, 7); + + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Q: /f/[b >= data[3]] + rc = jql_set_str(q, 0, 0, data[3]); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_list4(db, q, 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "[INDEX] SELECTED STR|6 /f/b EXPR1: 'b >= :?' " + "INIT: IWKV_CURSOR_GE STEP: IWKV_CURSOR_PREV")); + + i = 1; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + JBL jbl1, jbl2; + rc = jbl_at(doc->raw, "/f/b", &jbl1); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jbl_at(doc->raw, "/n", &jbl2); + CU_ASSERT_EQUAL_FATAL(rc, 0); + int64_t llv = jbl_get_i64(jbl2); + const char *str = jbl_get_str(jbl1); + CU_ASSERT_EQUAL(llv, 5); + CU_ASSERT_STRING_EQUAL(str, data[3]); + jbl_destroy(&jbl1); + jbl_destroy(&jbl2); + } + CU_ASSERT_EQUAL(i, 2); + + // todo: record update + // todo: record removal + + ejdb_list_destroy(&list); + iwxstr_clear(log); + jql_destroy(&q); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_destroy(log); + iwxstr_destroy(xstr); +} + +// Test array index +static void ejdb_test3_4() { + EJDB_OPTS opts = { + .kv = { + .path = "ejdb_test3_4.db", + .oflags = IWKV_TRUNC + }, + .no_wal = true + }; + EJDB db; + char dbuf[1024]; + + int i = 0; + EJDB_LIST list = 0; + int64_t docId = 0; + + IWPOOL *pool = iwpool_create(1024); + CU_ASSERT_PTR_NOT_NULL_FATAL(pool); + IWXSTR *log = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(log); + IWXSTR *xstr = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(xstr); + + iwrc rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_ensure_index(db, "a3", "/tags", EJDB_IDX_STR); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + snprintf(dbuf, sizeof(dbuf), "{\"tags\": [\"foo\", \"bar\", \"gaz\"],\"n\":%d}", 1); + rc = put_json(db, "a3", dbuf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + snprintf(dbuf, sizeof(dbuf), "{\"tags\": [\"gaz\", \"zaz\"],\"n\":%d}", 2); + rc = put_json2(db, "a3", dbuf, &docId); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + JQL q; + rc = jql_create(&q, "a3", "/tags/[** in :tags]"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + // Q: + JBL_NODE qtags; + rc = jbn_from_json("[\"zaz\",\"gaz\"]", &qtags, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jql_set_json(q, "tags", 0, qtags); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_list4(db, q, 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), + "[INDEX] SELECTED STR|5 /tags EXPR1: '** in :tags' " + "INIT: IWKV_CURSOR_EQ")); + i = 1; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + if (i == 1) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"tags\":[\"foo\",\"bar\",\"gaz\"],\"n\":1}"); + } else if ((i == 2) || (i == 3)) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"tags\":[\"gaz\",\"zaz\"],\"n\":2}"); + } + } + CU_ASSERT_EQUAL(i, 4); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + + // Get + CU_ASSERT_TRUE(docId > 0); + JBL jbl; + rc = ejdb_get(db, "a3", docId, &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_clear(xstr); + rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + jbl_destroy(&jbl); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"tags\":[\"gaz\",\"zaz\"],\"n\":2}"); + rc = put_json2(db, "a3", "{\"tags\": [\"gaz\",\"zaz\"],\"n\":2}", &docId); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + // Update {"tags":["gaz","zaz", "boo"], "n":2} + rc = put_json2(db, "a3", "{\"tags\": [\"gaz\",\"zaz\",\"boo\"],\"n\":2}", &docId); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + // Q: + rc = jbn_from_json("[\"zaz\",\"boo\"]", &qtags, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jql_set_json(q, "tags", 0, qtags); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_list4(db, q, 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), + "[INDEX] SELECTED STR|6 /tags EXPR1: '** in :tags' " + "INIT: IWKV_CURSOR_EQ")); + i = 1; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"tags\":[\"gaz\",\"zaz\",\"boo\"],\"n\":2}"); + } + CU_ASSERT_EQUAL(i, 3); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + // Remove last + rc = ejdb_del(db, "a3", docId); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + // G2 + rc = jbn_from_json("[\"gaz\"]", &qtags, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jql_set_json(q, "tags", 0, qtags); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_list4(db, q, 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), + "[INDEX] SELECTED STR|3 /tags EXPR1: '** in :tags' " + "INIT: IWKV_CURSOR_EQ")); + i = 1; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"tags\":[\"foo\",\"bar\",\"gaz\"],\"n\":1}"); + } + CU_ASSERT_EQUAL(i, 2); + + ejdb_list_destroy(&list); + iwxstr_clear(log); + + + jql_destroy(&q); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_destroy(log); + iwxstr_destroy(xstr); + iwpool_destroy(pool); +} + +void ejdb_test3_5() { + EJDB_OPTS opts = { + .kv = { + .path = "ejdb_test3_5.db", + .oflags = IWKV_TRUNC + }, + .no_wal = true + }; + EJDB db; + EJDB_LIST list = 0; + char dbuf[1024]; + IWXSTR *xstr = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(xstr); + + iwrc rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + for (int i = 1; i <= 10; ++i) { + snprintf(dbuf, sizeof(dbuf), "{\"f\":{\"b\":%d},\"n\":%d}", i, i); + rc = put_json(db, "c1", dbuf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + } + + rc = ejdb_list3(db, "c1", "/f/[b = 2] | del", 0, 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + int i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":2},\"n\":2}"); + } + CU_ASSERT_EQUAL_FATAL(i, 1); + ejdb_list_destroy(&list); + + // Check if /f/[b = 2] has been deleted + rc = ejdb_list3(db, "c1", "/f/[b = 2]", 0, 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NULL(list->first); + ejdb_list_destroy(&list); + + // Ensure index on /f/b + rc = ejdb_ensure_index(db, "c2", "/f/b", EJDB_IDX_UNIQUE | EJDB_IDX_I64); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_list3(db, "c1", "/f/[b = 3] | del", 0, 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + ejdb_list_destroy(&list); + + // Check if /f/[b = 3] has been deleted + rc = ejdb_list3(db, "c1", "/f/[b = 3]", 0, 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NULL(list->first); + ejdb_list_destroy(&list); + + rc = ejdb_list3(db, "c1", "/* | asc /f/b", 0, 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + i = 0; + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + iwxstr_clear(xstr); + rc = jbl_as_json(doc->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + if (i == 0) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":1},\"n\":1}"); + } else if (i == 1) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":4},\"n\":4}"); + } else if (i == 7) { + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"f\":{\"b\":10},\"n\":10}"); + } + } + ejdb_list_destroy(&list); + CU_ASSERT_EQUAL_FATAL(i, 8); + + // Remove rest of elements + rc = ejdb_list3(db, "c1", "/* | del | desc /f/b", 0, 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + ejdb_list_destroy(&list); + + // Check coll is empty + rc = ejdb_list3(db, "c1", "/*", 0, 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NULL(list->first); + ejdb_list_destroy(&list); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_destroy(xstr); +} + +static void jql_free_str(void *ptr, void *op) { + if (ptr) { + free(ptr); + } +} + +void ejdb_test3_6() { + EJDB_OPTS opts = { + .kv = { + .path = "ejdb_test3_6.db", + .oflags = IWKV_TRUNC + } + }; + + JQL q; + EJDB db; + EJDB_LIST list = 0; + IWXSTR *xstr = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(xstr); + + iwrc rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_ensure_index(db, "mycoll", "/foo", EJDB_IDX_UNIQUE | EJDB_IDX_STR); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + + rc = put_json(db, "mycoll", "{\"foo\":\"baz\",\"baz\":\"qux\"}"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jql_create(&q, 0, "@mycoll/[foo re :?]"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jql_set_regexp(q, 0, 0, ".*"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_list4(db, q, 0, 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL_FATAL(list->first); + + iwxstr_clear(xstr); + rc = jbl_as_json(list->first->raw, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr), "{\"foo\":\"baz\",\"baz\":\"qux\"}"); + ejdb_list_destroy(&list); + + // Now set regexp again + rc = jql_set_regexp2(q, 0, 0, strdup(".*"), jql_free_str, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_list4(db, q, 0, 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL_FATAL(list->first); + + ejdb_list_destroy(&list); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_destroy(xstr); + jql_destroy(&q); +} + +void ejdb_test3_7() { + EJDB_OPTS opts = { + .kv = { + .path = "ejdb_test3_7.db", + .oflags = IWKV_TRUNC + } + }; + EJDB db; + iwrc rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = put_json(db, "cc1", "{'foo':1}"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_rename_collection(db, "cc1", "cc2"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + JBL jbl; + rc = ejdb_get(db, "cc2", 1, &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + jbl_destroy(&jbl); + + rc = ejdb_rename_collection(db, "cc1", "cc2"); + CU_ASSERT_EQUAL_FATAL(rc, EJDB_ERROR_COLLECTION_NOT_FOUND); + + rc = ejdb_rename_collection(db, "cc2", "cc2"); + CU_ASSERT_EQUAL_FATAL(rc, EJDB_ERROR_TARGET_COLLECTION_EXISTS); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + opts.kv.oflags = 0; + + rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_get(db, "cc2", 1, &jbl); + CU_ASSERT_EQUAL_FATAL(rc, 0); + jbl_destroy(&jbl); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); +} + +void ejdb_test3_8(void) { + EJDB_OPTS opts = { + .kv = { + .path = "ejdb_test3_8.db", + .oflags = IWKV_TRUNC + }, + .no_wal = true + }; + + EJDB db; + JQL q; + char buf[64]; + JBL_NODE n; + + int64_t id1 = 0, id2 = 0; + EJDB_LIST list = 0; + + IWPOOL *pool = iwpool_create(255); + IWXSTR *log = iwxstr_new(); + CU_ASSERT_PTR_NOT_NULL_FATAL(log); + + iwrc rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = put_json2(db, "users", "{'name':'Andy'}", &id1); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = put_json2(db, "users", "{'name':'John'}", &id2); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jql_create(&q, "users", "/=:?"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jql_set_i64(q, 0, 0, id1); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_list4(db, q, 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + CU_ASSERT_PTR_NOT_NULL(strstr(iwxstr_ptr(log), "[INDEX] PK [COLLECTOR] PLAIN")); + CU_ASSERT_PTR_NOT_NULL(list->first); + CU_ASSERT_PTR_NULL(list->first->next); + + jql_destroy(&q); + ejdb_list_destroy(&list); + iwxstr_clear(log); + + rc = jql_create(&q, 0, "@users/=:id"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jql_set_str(q, "id", 0, "1"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = ejdb_list4(db, q, 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(list->first); + CU_ASSERT_PTR_NULL(list->first->next); + jql_destroy(&q); + ejdb_list_destroy(&list); + + // matching against PK array + snprintf(buf, sizeof(buf), "@users/=[%" PRId64 ",%" PRId64 "]", id1, id2); + rc = jql_create(&q, 0, buf); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = ejdb_list4(db, q, 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(list->first); + CU_ASSERT_PTR_NOT_NULL(list->first->next); + jql_destroy(&q); + ejdb_list_destroy(&list); + + // matching against PK array as JSON query paramater + snprintf(buf, sizeof(buf), "[%" PRId64 ",%" PRId64 "]", id1, id2); + rc = jbn_from_json(buf, &n, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jql_create(&q, 0, "@users/=:?"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jql_set_json(q, 0, 0, n); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = ejdb_list4(db, q, 0, log, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_PTR_NOT_NULL(list->first); + CU_ASSERT_PTR_NOT_NULL(list->first->next); + jql_destroy(&q); + ejdb_list_destroy(&list); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_destroy(log); + iwpool_destroy(pool); +} + +int main() { + CU_pSuite pSuite = NULL; + if (CUE_SUCCESS != CU_initialize_registry()) { + return CU_get_error(); + } + pSuite = CU_add_suite("ejdb_test3", init_suite, clean_suite); + if (NULL == pSuite) { + CU_cleanup_registry(); + return CU_get_error(); + } + if ( (NULL == CU_add_test(pSuite, "ejdb_test3_1", ejdb_test3_1)) + || (NULL == CU_add_test(pSuite, "ejdb_test3_2", ejdb_test3_2)) + || (NULL == CU_add_test(pSuite, "ejdb_test3_3", ejdb_test3_3)) + || (NULL == CU_add_test(pSuite, "ejdb_test3_4", ejdb_test3_4)) + || (NULL == CU_add_test(pSuite, "ejdb_test3_5", ejdb_test3_5)) + || (NULL == CU_add_test(pSuite, "ejdb_test3_6", ejdb_test3_6)) + || (NULL == CU_add_test(pSuite, "ejdb_test3_7", ejdb_test3_7)) + || (NULL == CU_add_test(pSuite, "ejdb_test3_8", ejdb_test3_8))) { + CU_cleanup_registry(); + return CU_get_error(); + } + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + int ret = CU_get_error() || CU_get_number_of_failures(); + CU_cleanup_registry(); + return ret; +} diff --git a/src/tests/ejdb_test4.c b/src/tests/ejdb_test4.c new file mode 100644 index 0000000..33a29bf --- /dev/null +++ b/src/tests/ejdb_test4.c @@ -0,0 +1,232 @@ +#include "ejdb_test.h" +#include + +#include + +int init_suite(void) { + iwrc rc = ejdb_init(); + return rc; +} + +int clean_suite() { + return 0; +} + +static void free_iwpool(void *ptr, void *op) { + iwpool_destroy((IWPOOL*) op); +} + +static void set_apply_int(JQL q, int idx, const char *key, int64_t id) { + JBL_NODE n; + IWPOOL *pool = iwpool_create(64); + iwrc rc = jbn_from_json("{}", &n, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jbn_add_item_i64(n, key, id, 0, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jql_set_json2(q, 0, idx, n, free_iwpool, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); +} + +static void ejdb_test4_1(void) { + EJDB_OPTS opts = { + .kv = { + .path = "ejdb_test4_1.db", + .oflags = IWKV_TRUNC + }, + .no_wal = true + }; + + EJDB db; + JQL q; + int64_t id = 0; + EJDB_LIST list = 0; + IWXSTR *xstr = iwxstr_new(); + + iwrc rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = put_json2(db, "artists", "{'name':'Leonardo Da Vinci', 'years':[1452,1519]}", &id); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = put_json(db, "paintings", "{'name':'Mona Lisa', 'year':1490, 'origin':'Italy'}"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jql_create(&q, "paintings", "/[name=:?] | apply :?"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jql_set_str(q, 0, 0, "Mona Lisa"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + set_apply_int(q, 1, "artist_ref", id); + rc = ejdb_update(db, q); + CU_ASSERT_EQUAL_FATAL(rc, 0); + jql_destroy(&q); + + rc = put_json(db, "paintings", "{'name':'Madonna Litta - Madonna And The Child', 'year':1490, 'origin':'Italy'}"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jql_create(&q, "paintings", "/[name=:?] | apply :?"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + rc = jql_set_str(q, 0, 0, "Madonna Litta - Madonna And The Child"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + set_apply_int(q, 1, "artist_ref", id); + rc = ejdb_update(db, q); + CU_ASSERT_EQUAL_FATAL(rc, 0); + jql_destroy(&q); + id = 0; + + rc = jql_create(&q, "paintings", "/* | /artist_reffirst; doc; doc = doc->next) { + JBL_NODE n; + iwxstr_clear(xstr); + rc = jbn_as_json(doc->node, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + //fprintf(stderr, "%s\n", iwxstr_ptr(xstr)); + rc = jbn_at(doc->node, "/artist_ref/name", &n); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_EQUAL(n->type, JBV_STR); + CU_ASSERT_NSTRING_EQUAL(n->vptr, "Leonardo Da Vinci", n->vsize); + + rc = jbn_at(doc->node, "/year", &n); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_EQUAL(n->type, JBV_I64); + CU_ASSERT_EQUAL(n->vi64, 1490); + } + jql_destroy(&q); + ejdb_list_destroy(&list); + + + // Next mode + rc = jql_create(&q, "paintings", "/* | /{name, artist_reffirst; doc; doc = doc->next) { + JBL_NODE n; + iwxstr_clear(xstr); + rc = jbn_as_json(doc->node, jbl_xstr_json_printer, xstr, 0); + CU_ASSERT_EQUAL_FATAL(rc, 0); + //fprintf(stderr, "%s\n", iwxstr_ptr(xstr)); + + rc = jbn_at(doc->node, "/name", &n); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_EQUAL(n->type, JBV_STR); + + rc = jbn_at(doc->node, "/artist_ref/name", &n); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_EQUAL(n->type, JBV_STR); + CU_ASSERT_NSTRING_EQUAL(n->vptr, "Leonardo Da Vinci", n->vsize); + + rc = jbn_at(doc->node, "/artist_ref/years/1", &n); // todo: review + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_EQUAL(n->type, JBV_I64); + CU_ASSERT_EQUAL(n->vi64, 1519); + } + jql_destroy(&q); + ejdb_list_destroy(&list); + + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + iwxstr_destroy(xstr); +} + +static void ejdb_test4_2(void) { + EJDB_OPTS opts = { + .kv = { + .path = "ejdb_test4_2.db", + .oflags = IWKV_TRUNC + }, + .no_wal = true + }; + + EJDB db; + JQL q; + JBL_NODE n, n2; + int i = 0; + EJDB_LIST list = 0; + IWPOOL *pool = iwpool_create_empty(); + + char uuid[IW_UUID_STR_LEN + 1] = { 0 }; + iwu_uuid4_fill(uuid); + + iwrc rc = ejdb_open(&opts, &db); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_ensure_index(db, "users", "/uuid", EJDB_IDX_STR | EJDB_IDX_UNIQUE); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jql_create(&q, "users", "/[uuid = :?] | upsert :?"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jql_set_str(q, 0, 0, uuid); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbn_from_json("{}", &n, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbn_add_item_str(n, "uuid", uuid, -1, 0, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbn_add_item_str(n, "name", "a", -1, 0, pool); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jql_set_json(q, 0, 1, n); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_update(db, q); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = jbn_at(n, "/name", &n2); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + n2->vptr = "b"; + rc = jql_set_json(q, 0, 1, n); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_update(db, q); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + jql_destroy(&q); + rc = jql_create(&q, "users", "/* | /name"); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + rc = ejdb_list4(db, q, 0, 0, &list); + CU_ASSERT_EQUAL_FATAL(rc, 0); + + for (EJDB_DOC doc = list->first; doc; doc = doc->next, ++i) { + CU_ASSERT_PTR_NOT_NULL_FATAL(doc->node); + rc = jbn_at(doc->node, "/name", &n2); + CU_ASSERT_EQUAL_FATAL(rc, 0); + CU_ASSERT_STRING_EQUAL(n2->vptr, "b"); + } + CU_ASSERT_EQUAL(i, 1); + + jql_destroy(&q); + ejdb_list_destroy(&list); + iwpool_destroy(pool); + rc = ejdb_close(&db); + CU_ASSERT_EQUAL_FATAL(rc, 0); +} + +int main() { + CU_pSuite pSuite = NULL; + if (CUE_SUCCESS != CU_initialize_registry()) { + return CU_get_error(); + } + pSuite = CU_add_suite("ejdb_test4", init_suite, clean_suite); + if (NULL == pSuite) { + CU_cleanup_registry(); + return CU_get_error(); + } + if ( (NULL == CU_add_test(pSuite, "ejdb_test4_1", ejdb_test4_1)) + || (NULL == CU_add_test(pSuite, "ejdb_test4_2", ejdb_test4_2))) { + CU_cleanup_registry(); + return CU_get_error(); + } + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + int ret = CU_get_error() || CU_get_number_of_failures(); + CU_cleanup_registry(); + return ret; +} diff --git a/src/tests/package.json b/src/tests/package.json new file mode 100644 index 0000000..f16ab7a --- /dev/null +++ b/src/tests/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "@react-native-community/eslint-config": "^0.0.5", + "eslint": "^6.6.0" + } +} diff --git a/src/tests/yarn.lock b/src/tests/yarn.lock new file mode 100644 index 0000000..ded68fb --- /dev/null +++ b/src/tests/yarn.lock @@ -0,0 +1,1256 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" + integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/generator@^7.7.2": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.2.tgz#2f4852d04131a5e17ea4f6645488b5da66ebf3af" + integrity sha512-WthSArvAjYLz4TcbKOi88me+KmDJdKSlfwwN8CnUYn9jBkzhq0ZEPuBfkAWIvjJ3AdEV1Cf/+eSQTnp3IDJKlQ== + dependencies: + "@babel/types" "^7.7.2" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + +"@babel/helper-function-name@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.7.0.tgz#44a5ad151cfff8ed2599c91682dda2ec2c8430a3" + integrity sha512-tDsJgMUAP00Ugv8O2aGEua5I2apkaQO7lBGUq1ocwN3G23JE5Dcq0uh3GvFTChPa4b40AWiAsLvCZOA2rdnQ7Q== + dependencies: + "@babel/helper-get-function-arity" "^7.7.0" + "@babel/template" "^7.7.0" + "@babel/types" "^7.7.0" + +"@babel/helper-get-function-arity@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.0.tgz#c604886bc97287a1d1398092bc666bc3d7d7aa2d" + integrity sha512-tLdojOTz4vWcEnHWHCuPN5P85JLZWbm5Fx5ZsMEMPhF3Uoe3O7awrbM2nQ04bDOUToH/2tH/ezKEOR8zEYzqyw== + dependencies: + "@babel/types" "^7.7.0" + +"@babel/helper-split-export-declaration@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.0.tgz#1365e74ea6c614deeb56ebffabd71006a0eb2300" + integrity sha512-HgYSI8rH08neWlAH3CcdkFg9qX9YsZysZI5GD8LjhQib/mM0jGOZOVkoUiiV2Hu978fRtjtsGsW6w0pKHUWtqA== + dependencies: + "@babel/types" "^7.7.0" + +"@babel/highlight@^7.0.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" + integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.0.0", "@babel/parser@^7.7.0", "@babel/parser@^7.7.2": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.2.tgz#ea8334dc77416bfd9473eb470fd00d8245b3943b" + integrity sha512-DDaR5e0g4ZTb9aP7cpSZLkACEBdoLGwJDWgHtBhrGX7Q1RjhdoMOfexICj5cqTAtpowjGQWfcvfnQG7G2kAB5w== + +"@babel/template@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.0.tgz#4fadc1b8e734d97f56de39c77de76f2562e597d0" + integrity sha512-OKcwSYOW1mhWbnTBgQY5lvg1Fxg+VyfQGjcBduZFljfc044J5iDlnDSfhQ867O17XHiSCxYHUxHg2b7ryitbUQ== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/types" "^7.7.0" + +"@babel/traverse@^7.0.0": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.7.2.tgz#ef0a65e07a2f3c550967366b3d9b62a2dcbeae09" + integrity sha512-TM01cXib2+rgIZrGJOLaHV/iZUAxf4A0dt5auY6KNZ+cm6aschuJGqKJM3ROTt3raPUdIDk9siAufIFEleRwtw== + dependencies: + "@babel/code-frame" "^7.5.5" + "@babel/generator" "^7.7.2" + "@babel/helper-function-name" "^7.7.0" + "@babel/helper-split-export-declaration" "^7.7.0" + "@babel/parser" "^7.7.2" + "@babel/types" "^7.7.2" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" + +"@babel/types@^7.0.0", "@babel/types@^7.7.0", "@babel/types@^7.7.2": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.7.2.tgz#550b82e5571dcd174af576e23f0adba7ffc683f7" + integrity sha512-YTf6PXoh3+eZgRCBzzP25Bugd2ngmpQVrk7kXX0i5N9BO7TFBtIgZYs7WtxtOGs8e6A4ZI7ECkbBCEHeXocvOA== + dependencies: + esutils "^2.0.2" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + +"@react-native-community/eslint-config@^0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@react-native-community/eslint-config/-/eslint-config-0.0.5.tgz#584f6493258202a57efc22e7be66966e43832795" + integrity sha512-jwO2tnKaTPTLX5XYXMHGEnFdf543SU7jz98/OF5mDH3b7lP+BOaCD+jVfqqHoDRkcqyPlYiR1CgwVGWpi0vMWg== + dependencies: + "@typescript-eslint/eslint-plugin" "^1.5.0" + "@typescript-eslint/parser" "^1.5.0" + babel-eslint "10.0.1" + eslint-plugin-eslint-comments "^3.1.1" + eslint-plugin-flowtype "2.50.3" + eslint-plugin-jest "22.4.1" + eslint-plugin-prettier "2.6.2" + eslint-plugin-react "7.12.4" + eslint-plugin-react-hooks "^1.5.1" + eslint-plugin-react-native "3.6.0" + prettier "1.16.4" + +"@types/eslint-visitor-keys@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" + integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== + +"@types/json-schema@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636" + integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A== + +"@typescript-eslint/eslint-plugin@^1.5.0": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.13.0.tgz#22fed9b16ddfeb402fd7bcde56307820f6ebc49f" + integrity sha512-WQHCozMnuNADiqMtsNzp96FNox5sOVpU8Xt4meaT4em8lOG1SrOv92/mUbEHQVh90sldKSfcOc/I0FOb/14G1g== + dependencies: + "@typescript-eslint/experimental-utils" "1.13.0" + eslint-utils "^1.3.1" + functional-red-black-tree "^1.0.1" + regexpp "^2.0.1" + tsutils "^3.7.0" + +"@typescript-eslint/experimental-utils@1.13.0": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz#b08c60d780c0067de2fb44b04b432f540138301e" + integrity sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "1.13.0" + eslint-scope "^4.0.0" + +"@typescript-eslint/parser@^1.5.0": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-1.13.0.tgz#61ac7811ea52791c47dc9fd4dd4a184fae9ac355" + integrity sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ== + dependencies: + "@types/eslint-visitor-keys" "^1.0.0" + "@typescript-eslint/experimental-utils" "1.13.0" + "@typescript-eslint/typescript-estree" "1.13.0" + eslint-visitor-keys "^1.0.0" + +"@typescript-eslint/typescript-estree@1.13.0": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz#8140f17d0f60c03619798f1d628b8434913dc32e" + integrity sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw== + dependencies: + lodash.unescape "4.0.1" + semver "5.5.0" + +acorn-jsx@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384" + integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw== + +acorn@^7.1.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" + integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== + +ajv@^6.10.0, ajv@^6.10.2: + version "6.10.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" + integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.2.1.tgz#4dccdb846c3eee10f6d64dea66273eab90c37228" + integrity sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q== + dependencies: + type-fest "^0.5.2" + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +array-includes@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" + integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0= + dependencies: + define-properties "^1.1.2" + es-abstract "^1.7.0" + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +babel-eslint@10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed" + integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.0.0" + "@babel/traverse" "^7.0.0" + "@babel/types" "^7.0.0" + eslint-scope "3.7.1" + eslint-visitor-keys "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-width@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +debug@^4.0.1, debug@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +es-abstract@^1.15.0, es-abstract@^1.7.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.16.0.tgz#d3a26dc9c3283ac9750dca569586e976d9dcc06d" + integrity sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg== + dependencies: + es-to-primitive "^1.2.0" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.0" + is-callable "^1.1.4" + is-regex "^1.0.4" + object-inspect "^1.6.0" + object-keys "^1.1.1" + string.prototype.trimleft "^2.1.0" + string.prototype.trimright "^2.1.0" + +es-to-primitive@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" + integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-plugin-eslint-comments@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.1.2.tgz#4ef6c488dbe06aa1627fea107b3e5d059fc8a395" + integrity sha512-QexaqrNeteFfRTad96W+Vi4Zj1KFbkHHNMMaHZEYcovKav6gdomyGzaxSDSL3GoIyUOo078wRAdYlu1caiauIQ== + dependencies: + escape-string-regexp "^1.0.5" + ignore "^5.0.5" + +eslint-plugin-flowtype@2.50.3: + version "2.50.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.50.3.tgz#61379d6dce1d010370acd6681740fd913d68175f" + integrity sha512-X+AoKVOr7Re0ko/yEXyM5SSZ0tazc6ffdIOocp2fFUlWoDt7DV0Bz99mngOkAFLOAWjqRA5jPwqUCbrx13XoxQ== + dependencies: + lodash "^4.17.10" + +eslint-plugin-jest@22.4.1: + version "22.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.4.1.tgz#a5fd6f7a2a41388d16f527073b778013c5189a9c" + integrity sha512-gcLfn6P2PrFAVx3AobaOzlIEevpAEf9chTpFZz7bYfc7pz8XRv7vuKTIE4hxPKZSha6XWKKplDQ0x9Pq8xX2mg== + +eslint-plugin-prettier@2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.2.tgz#71998c60aedfa2141f7bfcbf9d1c459bf98b4fad" + integrity sha512-tGek5clmW5swrAx1mdPYM8oThrBE83ePh7LeseZHBWfHVGrHPhKn7Y5zgRMbU/9D5Td9K4CEmUPjGxA7iw98Og== + dependencies: + fast-diff "^1.1.1" + jest-docblock "^21.0.0" + +eslint-plugin-react-hooks@^1.5.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz#6210b6d5a37205f0b92858f895a4e827020a7d04" + integrity sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA== + +eslint-plugin-react-native-globals@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz#ee1348bc2ceb912303ce6bdbd22e2f045ea86ea2" + integrity sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g== + +eslint-plugin-react-native@3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-native/-/eslint-plugin-react-native-3.6.0.tgz#7cad3b7c6159df6d26fe3252c6c5417a17f27b4b" + integrity sha512-BEQcHZ06hZSBYWFVuNEq0xuui5VEsWpHDsZGBtfadHfCRqRMUrkYPgdDb3bpc60qShHE83kqIv59uKdinEg91Q== + dependencies: + eslint-plugin-react-native-globals "^0.1.1" + +eslint-plugin-react@7.12.4: + version "7.12.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz#b1ecf26479d61aee650da612e425c53a99f48c8c" + integrity sha512-1puHJkXJY+oS1t467MjbqjvX53uQ05HXwjqDgdbGBqf5j9eeydI54G3KwiJmWciQ0HTBacIKw2jgwSBSH3yfgQ== + dependencies: + array-includes "^3.0.3" + doctrine "^2.1.0" + has "^1.0.3" + jsx-ast-utils "^2.0.1" + object.fromentries "^2.0.0" + prop-types "^15.6.2" + resolve "^1.9.0" + +eslint-scope@3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug= + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-scope@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-scope@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" + integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-utils@^1.3.1, eslint-utils@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" + integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== + +eslint@^6.6.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.6.0.tgz#4a01a2fb48d32aacef5530ee9c5a78f11a8afd04" + integrity sha512-PpEBq7b6qY/qrOmpYQ/jTMDYfuQMELR4g4WI1M/NaSDDD/bdcMb+dj4Hgks7p41kW2caXsPsEZAEAyAgjVVC0g== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^1.4.3" + eslint-visitor-keys "^1.1.0" + espree "^6.1.2" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^11.7.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^7.0.0" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.14" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^6.1.2" + strip-ansi "^5.2.0" + strip-json-comments "^3.0.1" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.2.tgz#6c272650932b4f91c3714e5e7b5f5e2ecf47262d" + integrity sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA== + dependencies: + acorn "^7.1.0" + acorn-jsx "^5.1.0" + eslint-visitor-keys "^1.1.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" + integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== + dependencies: + estraverse "^4.0.0" + +esrecurse@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== + dependencies: + estraverse "^4.1.0" + +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + +fast-diff@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +figures@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.1.0.tgz#4b198dd07d8d71530642864af2d45dd9e459c4ec" + integrity sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" + integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +glob-parent@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" + integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0, globals@^11.7.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= + +has@^1.0.1, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.0.5: + version "5.1.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" + integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== + +import-fresh@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.1.0.tgz#6d33fa1dcef6df930fae003446f33415af905118" + integrity sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inquirer@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.0.tgz#9e2b032dde77da1db5db804758b8fea3a970519a" + integrity sha512-rSdC7zelHdRQFkWnhsMu2+2SO41mpv2oF2zy4tMhmiLWkcKbOAs87fWAJhVXttKVwhdZvymvnuM95EyEXg2/tQ== + dependencies: + ansi-escapes "^4.2.1" + chalk "^2.4.2" + cli-cursor "^3.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.15" + mute-stream "0.0.8" + run-async "^2.2.0" + rxjs "^6.4.0" + string-width "^4.1.0" + strip-ansi "^5.1.0" + through "^2.3.6" + +is-callable@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" + integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== + +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= + +is-regex@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= + dependencies: + has "^1.0.1" + +is-symbol@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" + integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== + dependencies: + has-symbols "^1.0.0" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +jest-docblock@^21.0.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414" + integrity sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +jsx-ast-utils@^2.0.1: + version "2.2.3" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz#8a9364e402448a3ce7f14d357738310d9248054f" + integrity sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA== + dependencies: + array-includes "^3.0.3" + object.assign "^4.1.0" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lodash.unescape@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" + integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw= + +lodash@^4.17.10, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-inspect@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" + integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.fromentries@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.1.tgz#050f077855c7af8ae6649f45c80b16ee2d31e704" + integrity sha512-PUQv8Hbg3j2QX0IQYv3iAGCbGcu4yY4KQ92/dhA4sFSixBmSmp13UpDLs6jGK8rBtbmhNNIK99LD2k293jpiGA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.15.0" + function-bind "^1.1.1" + has "^1.0.3" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" + integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.8.2: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prettier@1.16.4: + version "1.16.4" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717" + integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +prop-types@^15.6.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +react-is@^16.8.1: + version "16.11.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.11.0.tgz#b85dfecd48ad1ce469ff558a882ca8e8313928fa" + integrity sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw== + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.9.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" + integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== + dependencies: + path-parse "^1.0.6" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= + dependencies: + is-promise "^2.1.0" + +rxjs@^6.4.0: + version "6.5.3" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a" + integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA== + dependencies: + tslib "^1.9.0" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" + integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA== + +semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.1.2: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.1.0.tgz#ba846d1daa97c3c596155308063e075ed1c99aff" + integrity sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^5.2.0" + +string.prototype.trimleft@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634" + integrity sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string.prototype.trimright@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58" + integrity sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-json-comments@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" + integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +tslib@^1.8.1, tslib@^1.9.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + +tsutils@^3.7.0: + version "3.17.1" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" + integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== + dependencies: + tslib "^1.8.1" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-fest@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.5.2.tgz#d6ef42a0356c6cd45f49485c3b6281fc148e48a2" + integrity sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw== + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +v8-compile-cache@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" + integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" diff --git a/src/tmpl/ejdb2cfg.h b/src/tmpl/ejdb2cfg.h new file mode 100644 index 0000000..00ff00e --- /dev/null +++ b/src/tmpl/ejdb2cfg.h @@ -0,0 +1,57 @@ +#pragma once +#ifndef JBCFG_H +#define JBCFG_H + +/************************************************************************************************** + * EJDB2 + * + * MIT License + * + * Copyright (c) 2012-2021 Softmotions Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *************************************************************************************************/ + +#if !defined(IW_32) && !defined(IW_64) +#error Unknown CPU bits +#endif + +#define EJDB2_GIT_REVISION "@GIT_REVISION@" +#define EJDB2_VERSION "@ejdb2_VERSION@" +#define EJDB2_VERSION_MAJOR @ejdb2_VERSION_MAJOR@ +#define EJDB2_VERSION_MINOR @ejdb2_VERSION_MINOR@ +#define EJDB2_VERSION_PATCH @ejdb2_VERSION_PATCH@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define JBNUMBUF_SIZE 64 + +#ifndef static_assert +#define static_assert _Static_assert +#endif + +#endif diff --git a/src/tmpl/libejdb2.pc.in b/src/tmpl/libejdb2.pc.in new file mode 100644 index 0000000..a452b0a --- /dev/null +++ b/src/tmpl/libejdb2.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix}/@CMAKE_INSTALL_BINDIR@ +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: @PROJECT_NAME@ +Description: @PROJECT_DESCRIPTION_SUMMARY@ +URL: @PROJECT_WEBSITE@ +Version: @PROJECT_VERSION@ +Libs: -L${libdir} -l@PROJECT_NAME@ +Cflags: -I${includedir} + diff --git a/src/util/convert.h b/src/util/convert.h new file mode 100644 index 0000000..429c919 --- /dev/null +++ b/src/util/convert.h @@ -0,0 +1,29 @@ +#pragma once +#ifndef CONVERT_H +#define CONVERT_H + +#include + +/** + * sizeof `buf` must be at least 64 bytes + */ +static void jbi_ftoa(long double val, char buf[static 64], size_t *osz) { + // todo: review + int sz = snprintf(buf, 64, "%.8Lf", val); + if (sz <= 0) { + buf[0] = '\0'; + *osz = 0; + return; + } + while (sz > 0 && buf[sz - 1] == '0') { // trim right zeroes + buf[sz - 1] = '\0'; + sz--; + } + if ((sz > 0) && (buf[sz - 1] == '.')) { + buf[sz - 1] = '\0'; + sz--; + } + *osz = (size_t) sz; +} + +#endif diff --git a/src/util/khash.h b/src/util/khash.h new file mode 100644 index 0000000..b70919c --- /dev/null +++ b/src/util/khash.h @@ -0,0 +1,619 @@ +// -V::1003 +#ifndef __AC_KHASH_H +#define __AC_KHASH_H + +/* The MIT License + + Copyright (c) 2008, 2009, 2011 by Attractive Chaos + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +/* + An example: + +#include "khash.h" +KHASH_MAP_INIT_INT(32, char) +int main() { + int ret, is_missing; + khiter_t k; + khash_t(32) *h = kh_init(32); + k = kh_put(32, h, 5, &ret); + kh_value(h, k) = 10; + k = kh_get(32, h, 10); + is_missing = (k == kh_end(h)); + k = kh_get(32, h, 5); + kh_del(32, h, k); + for (k = kh_begin(h); k != kh_end(h); ++k) + if (kh_exist(h, k)) kh_value(h, k) = 1; + kh_destroy(32, h); + return 0; +} +*/ + +/* + 2013-05-02 (0.2.8): + + * Use quadratic probing. When the capacity is power of 2, stepping function + i*(i+1)/2 guarantees to traverse each bucket. It is better than double + hashing on cache performance and is more robust than linear probing. + + In theory, double hashing should be more robust than quadratic probing. + However, my implementation is probably not for large hash tables, because + the second hash function is closely tied to the first hash function, + which reduce the effectiveness of double hashing. + + Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php + + 2011-12-29 (0.2.7): + + * Minor code clean up; no actual effect. + + 2011-09-16 (0.2.6): + + * The capacity is a power of 2. This seems to dramatically improve the + speed for simple keys. Thank Zilong Tan for the suggestion. Reference: + + - http://code.google.com/p/ulib/ + - http://nothings.org/computer/judy/ + + * Allow to optionally use linear probing which usually has better + performance for random input. Double hashing is still the default as it + is more robust to certain non-random input. + + * Added Wang's integer hash function (not used by default). This hash + function is more robust to certain non-random input. + + 2011-02-14 (0.2.5): + + * Allow to declare global functions. + + 2009-09-26 (0.2.4): + + * Improve portability + + 2008-09-19 (0.2.3): + + * Corrected the example + * Improved interfaces + + 2008-09-11 (0.2.2): + + * Improved speed a little in kh_put() + + 2008-09-10 (0.2.1): + + * Added kh_clear() + * Fixed a compiling error + + 2008-09-02 (0.2.0): + + * Changed to token concatenation which increases flexibility. + + 2008-08-31 (0.1.2): + + * Fixed a bug in kh_get(), which has not been tested previously. + + 2008-08-31 (0.1.1): + + * Added destructor +*/ + +/*! + @header + + Generic hash table library. + */ + +#define AC_VERSION_KHASH_H "0.2.8" + +#include +#include +#include + +/* compiler specific configuration */ + +#if UINT_MAX == 0xffffffffu +typedef unsigned int khint32_t; +#elif ULONG_MAX == 0xffffffffu +typedef unsigned long khint32_t; +#endif + +#if ULONG_MAX == ULLONG_MAX +typedef unsigned long khint64_t; +#else +typedef unsigned long long khint64_t; +#endif + +#ifndef kh_inline +#ifdef _MSC_VER +#define kh_inline __inline +#else +#define kh_inline inline +#endif +#endif /* kh_inline */ + +typedef khint32_t khint_t; +typedef khint_t khiter_t; + +#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) +#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) +#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) +#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) +#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) +#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) +#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) + +#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) + +#ifndef kroundup32 +#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) +#endif + +#ifndef kcalloc +#define kcalloc(N,Z) calloc(N,Z) +#endif +#ifndef kmalloc +#define kmalloc(Z) malloc(Z) +#endif +#ifndef krealloc +#define krealloc(P,Z) realloc(P,Z) +#endif +#ifndef kfree +#define kfree(P) free(P) +#endif + +static const double __ac_HASH_UPPER = 0.77; + +#define __KHASH_TYPE(name, khkey_t, khval_t) \ + typedef struct kh_##name##_s { \ + khint_t n_buckets, size, n_occupied, upper_bound; \ + khint32_t *flags; \ + khkey_t *keys; \ + khval_t *vals; \ + } kh_##name##_t; + +#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ + extern kh_##name##_t *kh_init_##name(void); \ + extern void kh_destroy_##name(kh_##name##_t *h); \ + extern void kh_clear_##name(kh_##name##_t *h); \ + extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ + extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ + extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ + extern void kh_del_##name(kh_##name##_t *h, khint_t x); + +#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + SCOPE kh_##name##_t *kh_init_##name(void) { \ + return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ + } \ + SCOPE void kh_destroy_##name(kh_##name##_t *h) \ + { \ + if (h) { \ + kfree((void *)h->keys); kfree(h->flags); \ + kfree((void *)h->vals); \ + kfree(h); \ + } \ + } \ + SCOPE void kh_clear_##name(kh_##name##_t *h) \ + { \ + if (h && h->flags) { \ + memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ + h->size = h->n_occupied = 0; \ + } \ + } \ + SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ + { \ + if (h->n_buckets) { \ + khint_t k, i, last, mask, step = 0; \ + mask = h->n_buckets - 1; \ + k = __hash_func(key); i = k & mask; \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + i = (i + (++step)) & mask; \ + if (i == last) return h->n_buckets; \ + } \ + return __ac_iseither(h->flags, i)? h->n_buckets : i; \ + } else return 0; \ + } \ + SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ + { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ + khint32_t *new_flags = 0; \ + khint_t j = 1; \ + { \ + kroundup32(new_n_buckets); \ + if (new_n_buckets < 4) new_n_buckets = 4; \ + if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ + else { /* hash table size to be changed (shrink or expand); rehash */ \ + new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (!new_flags) return -1; \ + memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (h->n_buckets < new_n_buckets) { /* expand */ \ + khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (!new_keys) { kfree(new_flags); return -1; } \ + h->keys = new_keys; \ + if (kh_is_map) { \ + khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ + if (!new_vals) { kfree(new_flags); return -1; } \ + h->vals = new_vals; \ + } \ + } /* otherwise shrink */ \ + } \ + } \ + if (j) { /* rehashing is needed */ \ + for (j = 0; j != h->n_buckets; ++j) { \ + if (__ac_iseither(h->flags, j) == 0) { \ + khkey_t key = h->keys[j]; \ + khval_t val; \ + khint_t new_mask; \ + new_mask = new_n_buckets - 1; \ + if (kh_is_map) val = h->vals[j]; \ + __ac_set_isdel_true(h->flags, j); \ + while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ + khint_t k, i, step = 0; \ + k = __hash_func(key); \ + i = k & new_mask; \ + while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ + __ac_set_isempty_false(new_flags, i); \ + if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ + { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ + if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ + __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ + } else { /* write the element and jump out of the loop */ \ + h->keys[i] = key; \ + if (kh_is_map) h->vals[i] = val; \ + break; \ + } \ + } \ + } \ + } \ + if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ + h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ + } \ + kfree(h->flags); /* free the working space */ \ + h->flags = new_flags; \ + h->n_buckets = new_n_buckets; \ + h->n_occupied = h->size; \ + h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ + } \ + return 0; \ + } \ + SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ + { \ + khint_t x; \ + if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ + if (h->n_buckets > (h->size<<1)) { \ + if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ + *ret = -1; return h->n_buckets; \ + } \ + } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ + *ret = -1; return h->n_buckets; \ + } \ + } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ + { \ + khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ + x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ + if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ + else { \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + if (__ac_isdel(h->flags, i)) site = i; \ + i = (i + (++step)) & mask; \ + if (i == last) { x = site; break; } \ + } \ + if (x == h->n_buckets) { \ + if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ + else x = i; \ + } \ + } \ + } \ + if (__ac_isempty(h->flags, x)) { /* not present at all */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; ++h->n_occupied; \ + *ret = 1; \ + } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; \ + *ret = 2; \ + } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ + return x; \ + } \ + SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ + { \ + if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ + __ac_set_isdel_true(h->flags, x); \ + --h->size; \ + } \ + } + +#define KHASH_DECLARE(name, khkey_t, khval_t) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_PROTOTYPES(name, khkey_t, khval_t) + +#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +/* --- BEGIN OF HASH FUNCTIONS --- */ + +/*! @function + @abstract Integer hash function + @param key The integer [khint32_t] + @return The hash value [khint_t] + */ +#define kh_int_hash_func(key) (khint32_t)(key) +/*! @function + @abstract Integer comparison function + */ +#define kh_int_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract 64-bit integer hash function + @param key The integer [khint64_t] + @return The hash value [khint_t] + */ +#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) +/*! @function + @abstract 64-bit integer comparison function + */ +#define kh_int64_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract const char* hash function + @param s Pointer to a null terminated string + @return The hash value + */ +static kh_inline khint_t __ac_X31_hash_string(const char *s) +{ + khint_t h = (khint_t)*s; + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; + return h; +} +/*! @function + @abstract Another interface to const char* hash function + @param key Pointer to a null terminated string [const char*] + @return The hash value [khint_t] + */ +#define kh_str_hash_func(key) __ac_X31_hash_string(key) +/*! @function + @abstract Const char* comparison function + */ +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) + +static kh_inline khint_t __ac_Wang_hash(khint_t key) +{ + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + return key; +} +#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key) + +/* --- END OF HASH FUNCTIONS --- */ + +/* Other convenient macros... */ + +/*! + @abstract Type of the hash table. + @param name Name of the hash table [symbol] + */ +#define khash_t(name) kh_##name##_t + +/*! @function + @abstract Initiate a hash table. + @param name Name of the hash table [symbol] + @return Pointer to the hash table [khash_t(name)*] + */ +#define kh_init(name) kh_init_##name() + +/*! @function + @abstract Destroy a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_destroy(name, h) kh_destroy_##name(h) + +/*! @function + @abstract Reset a hash table without deallocating memory. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_clear(name, h) kh_clear_##name(h) + +/*! @function + @abstract Resize a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param s New size [khint_t] + */ +#define kh_resize(name, h, s) kh_resize_##name(h, s) + +/*! @function + @abstract Insert a key to the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @param r Extra return code: -1 if the operation failed; + 0 if the key is present in the hash table; + 1 if the bucket is empty (never used); 2 if the element in + the bucket has been deleted [int*] + @return Iterator to the inserted element [khint_t] + */ +#define kh_put(name, h, k, r) kh_put_##name(h, k, r) + +/*! @function + @abstract Retrieve a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] + */ +#define kh_get(name, h, k) kh_get_##name(h, k) + +/*! @function + @abstract Remove a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Iterator to the element to be deleted [khint_t] + */ +#define kh_del(name, h, k) kh_del_##name(h, k) + +/*! @function + @abstract Test whether a bucket contains data. + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return 1 if containing data; 0 otherwise [int] + */ +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) + +/*! @function + @abstract Get key given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Key [type of keys] + */ +#define kh_key(h, x) ((h)->keys[x]) + +/*! @function + @abstract Get value given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Value [type of values] + @discussion For hash sets, calling this results in segfault. + */ +#define kh_val(h, x) ((h)->vals[x]) + +/*! @function + @abstract Alias of kh_val() + */ +#define kh_value(h, x) ((h)->vals[x]) + +/*! @function + @abstract Get the start iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The start iterator [khint_t] + */ +#define kh_begin(h) (khint_t)(0) + +/*! @function + @abstract Get the end iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The end iterator [khint_t] + */ +#define kh_end(h) ((h)->n_buckets) + +/*! @function + @abstract Get the number of elements in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of elements in the hash table [khint_t] + */ +#define kh_size(h) ((h)->size) + +/*! @function + @abstract Get the number of buckets in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of buckets in the hash table [khint_t] + */ +#define kh_n_buckets(h) ((h)->n_buckets) + +/*! @function + @abstract Iterate over the entries in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param kvar Variable to which key will be assigned + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (kvar) = kh_key(h,__i); \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/*! @function + @abstract Iterate over the values in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach_value(h, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/* More conenient interfaces */ + +/*! @function + @abstract Instantiate a hash set containing integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT(name) \ + KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT(name, khval_t) \ + KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT64(name) \ + KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT64(name, khval_t) \ + KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) + +typedef const char *kh_cstr_t; +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_STR(name) \ + KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_STR(name, khval_t) \ + KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) + +#endif /* __AC_KHASH_H */ diff --git a/src/util/lwre.c b/src/util/lwre.c new file mode 100644 index 0000000..b90f379 --- /dev/null +++ b/src/util/lwre.c @@ -0,0 +1,988 @@ +// -V::506 + +/* Copyright (c) 2014 by Ian Piumarta + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the 'Software'), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, provided that the above copyright notice(s) and this + * permission notice appear in all copies of the Software. Acknowledgement + * of the use of this Software in supporting documentation would be + * appreciated but is not required. + * + * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. + */ + +#include +#include +#include +#include +#include + +#include "lwre.h" + +#ifndef RE_ERROR +# define RE_ERROR(RE, CODE, MESSAGE) { (RE)->error_message = (MESSAGE); \ + longjmp(*(RE)->error_env, (re->error_code = RE_ERROR_ ## CODE)); } +#endif + +#ifndef RE_MALLOC +# define RE_MALLOC(RE, SIZE) re__malloc((RE), (SIZE)) + +static void *re__malloc(struct re *re, size_t size) { + void *p = malloc(size); + if (!p) { + RE_ERROR(re, NOMEM, "out of memory"); + } + return p; +} + +#endif + +#ifndef RE_CALLOC +# define RE_CALLOC(RE, NMEMB, SIZE) re__calloc((RE), (NMEMB), (SIZE)) + +static void *re__calloc(struct re *re, size_t nmemb, size_t size) { + void *p = calloc(nmemb, size); + if (p) { + return p; + } + if (re) { + RE_ERROR(re, NOMEM, "out of memory"); + } + return p; +} + +#endif + +#ifndef RE_REALLOC +# define RE_REALLOC(RE, PTR, SIZE) re__realloc((RE), (PTR), (SIZE)) + +static inline void *re__realloc(struct re *re, void *ptr, size_t size) { + void *p = realloc(ptr, size); + if (!p) { + RE_ERROR(re, NOMEM, "out of memory"); + } + return p; +} + +#endif + +#ifndef RE_FREE +# define RE_FREE(RE, PTR) free(PTR) +#endif + +/* arrays */ + +#define re_array_of(TYPE) \ + struct { \ + int size; \ + int capacity; \ + TYPE *at; \ + } + +#define RE_ARRAY_OF_INITIALISER { 0, 0, 0 } + +#define re_array_append(RE, ARRAY, ELEMENT) \ + ((ARRAY).size++, \ + (((ARRAY).size > (ARRAY).capacity) \ + ? ((ARRAY).at = RE_REALLOC((RE), (ARRAY).at, \ + sizeof(*(ARRAY).at) * ((ARRAY).capacity = ((ARRAY).capacity \ + ? ((ARRAY).capacity * 2) \ + : 8)))) \ + : (ARRAY).at) \ + [(ARRAY).size - 1] = (ELEMENT)) + +#define re_array_copy(RE, ARRAY) \ + { (ARRAY).size, \ + (ARRAY).size, \ + memcpy(RE_MALLOC((RE), sizeof((ARRAY).at[0]) * (ARRAY).size), \ + (ARRAY).at, \ + sizeof((ARRAY).at[0]) * (ARRAY).size) } + +#define re_array_release(RE, ARRAY) { \ + if ((ARRAY).at) { \ + RE_FREE((RE), (ARRAY).at); \ + (ARRAY).at = 0; \ + } \ +} + +/* bit sets */ + +struct RE_BitSet; +typedef struct RE_BitSet RE_BitSet; + +struct RE_BitSet { + int inverted; + unsigned char bits[256 / sizeof(unsigned char)]; +}; + +static int re_bitset__includes(RE_BitSet *c, int i) { + if ((i < 0) || (255 < i)) { + return 0; + } + return (c->bits[i / 8] >> (i % 8)) & 1; +} + +static int re_bitset_includes(RE_BitSet *c, int i) { + int inc = re_bitset__includes(c, i); + if (c->inverted) { + inc = !inc; + } + return inc; +} + +static void re_bitset_add(RE_BitSet *c, int i) { + if ((i < 0) || (255 < i)) { + return; + } + c->bits[i / 8] |= (1 << (i % 8)); +} + +/* character classes */ + +static int re_make_char(struct re *re) { + const char *p = re->position; + if (!*p) { + return 0; + } + int c = *p++; + if (('\\' == c) && *p) { + c = *p++; + } + re->position = p; + return c; +} + +static RE_BitSet *re_make_class(struct re *re) { + RE_BitSet *c = RE_CALLOC(re, 1, sizeof(RE_BitSet)); + int last = -1; + c->inverted = ('^' == *re->position); // -V522 + if (c->inverted) { + re->position++; + } + while (*re->position && (']' != *re->position)) { + int this = re->position[0]; + if (('-' == this) && (last >= 0) && re->position[1] && (']' != re->position[1])) { + re->position++; + this = re_make_char(re); + do { + re_bitset_add(c, last++); + } while (last <= this); + last = -1; + } else { + this = re_make_char(re); + re_bitset_add(c, this); + last = this; + } + } + return c; +} + +/* instructions */ + +enum { RE_Any, RE_Char, RE_Class, RE_Accept, RE_Jump, RE_Fork, RE_Begin, RE_End, }; + +struct RE_Insn; +typedef struct RE_Insn RE_Insn; + +struct RE_Insn { + int opcode; + long x; + union { + long y; + RE_BitSet *c; + }; + union { + RE_Insn *next; + char *stamp; + }; +}; + +struct RE_Compiled; +typedef struct RE_Compiled RE_Compiled; + +/* + struct RE_Compiled + { + int size; + RE_Insn *first; + RE_Insn *last; + }; + + #define RE_COMPILED_INITIALISER { 0, 0, 0 } + */ + +static RE_Compiled re_insn_new(struct re *re, int opc) { + RE_Insn *insn = RE_CALLOC(re, 1, sizeof(RE_Insn)); + insn->opcode = opc; // -V522 + RE_Compiled insns = { 1, insn, insn }; + return insns; +} + +static RE_Compiled re_new_Any(struct re *re) { + RE_Compiled insns = re_insn_new(re, RE_Any); + return insns; +} + +static RE_Compiled re_new_Char(struct re *re, int c) { + RE_Compiled insns = re_insn_new(re, RE_Char); + insns.first->x = c; + return insns; +} + +static RE_Compiled re_new_Class(struct re *re, RE_BitSet *c) { + RE_Compiled insns = re_insn_new(re, RE_Class); + insns.first->c = c; + return insns; +} + +static RE_Compiled re_new_Accept(struct re *re) { + RE_Compiled insns = re_insn_new(re, RE_Accept); + return insns; +} + +static RE_Compiled re_new_Jump(struct re *re, int x) { + RE_Compiled insns = re_insn_new(re, RE_Jump); + insns.first->x = x; + return insns; +} + +static RE_Compiled re_new_Fork(struct re *re, int x, int y) { + RE_Compiled insns = re_insn_new(re, RE_Fork); + insns.first->x = x; + insns.first->y = y; + return insns; +} + +static RE_Compiled re_new_Begin(struct re *re) { + RE_Compiled insns = re_insn_new(re, RE_Begin); + return insns; +} + +static RE_Compiled re_new_End(struct re *re) { + RE_Compiled insns = re_insn_new(re, RE_End); + return insns; +} + +static void re_program_append(RE_Compiled *insns, RE_Compiled tail) { + insns->last->next = tail.first; + insns->last = tail.last; + insns->size += tail.size; +} + +static void re_program_prepend(RE_Compiled *insns, RE_Compiled head) { + head.last->next = insns->first; + insns->first = head.first; + insns->size += head.size; +} + +static void re_program_free(struct re *re, RE_Compiled *insns) { + int i; + for (i = 0; i < insns->size; ++i) { + switch (insns->first[i].opcode) { + case RE_Class: { + RE_FREE(re, insns->first[i].c); + insns->first[i].c = 0; + break; + } + } + } + RE_FREE(re, insns->first); + insns->first = insns->last = 0; + insns->size = 0; +} + +/* compilation */ + +/* + struct re + { + char *expression; + char *position; + jmp_buf *error_env; + int error_code; + char *error_message; + struct RE_Compiled code; + char **matches; + int nmatches; + }; + */ + +static RE_Compiled re_compile_expression(struct re *re); + +static RE_Compiled re_compile_primary(struct re *re) { + int c = *re->position++; + assert(0 != c); + switch (c) { + case '\\': { + if (*re->position) { + c = *re->position++; + } + break; + } + case '.': { + return re_new_Any(re); + } + case '[': { + RE_BitSet *cc = re_make_class(re); + if (']' != *re->position) { + RE_FREE(re, cc); + RE_ERROR(re, CHARSET, "expected ']' at end of character set"); + } + re->position++; + return re_new_Class(re, cc); + }; + case '(': { + RE_Compiled insns = re_compile_expression(re); + if (')' != *re->position) { + RE_Insn *insn, *next; + for (insn = insns.first; insn; insn = next) { + next = insn->next; + RE_FREE(re, insn); + } + RE_ERROR(re, SUBEXP, "expected ')' at end of subexpression"); + } + re->position++; + return insns; + } + case '{': { + RE_Compiled insns = re_compile_expression(re); + if ('}' != *re->position) { + RE_Insn *insn, *next; + for (insn = insns.first; insn; insn = next) { + next = insn->next; + RE_FREE(re, insn); + } + RE_ERROR(re, SUBMATCH, "expected '}' at end of submatch"); + } + re_program_prepend(&insns, re_new_Begin(re)); + re_program_append(&insns, re_new_End(re)); + re->position++; + return insns; + } + } + return re_new_Char(re, c); +} + +static RE_Compiled re_compile_suffix(struct re *re) { + RE_Compiled insns = re_compile_primary(re); + switch (*re->position) { + case '?': { + re->position++; + if ('?' == *re->position) { + re->position++; + re_program_prepend(&insns, re_new_Fork(re, insns.size, 0)); + } else { + re_program_prepend(&insns, re_new_Fork(re, 0, insns.size)); + } + break; + } + case '*': { + re->position++; + if ('?' == *re->position) { + re->position++; + re_program_prepend(&insns, re_new_Fork(re, insns.size + 1, 0)); + } else { + re_program_prepend(&insns, re_new_Fork(re, 0, insns.size + 1)); + } + re_program_append(&insns, re_new_Jump(re, -(insns.size + 1))); + break; + } + case '+': { + re->position++; + if ('?' == *re->position) { + re->position++; + re_program_append(&insns, re_new_Fork(re, 0, -(insns.size + 1))); + } else { + re_program_append(&insns, re_new_Fork(re, -(insns.size + 1), 0)); + } + break; + } + } + return insns; +} + +static RE_Compiled re_compile_sequence(struct re *re) { + if (!*re->position) { + return re_new_Accept(re); + } + RE_Compiled head = re_compile_suffix(re); + while (*re->position && !strchr("|)}>", *re->position)) + re_program_append(&head, re_compile_suffix(re)); + if (!*re->position) { + re_program_append(&head, re_new_Accept(re)); + } + return head; +} + +static RE_Compiled re_compile_expression(struct re *re) { + RE_Compiled head = re_compile_sequence(re); + while ('|' == *re->position) { + re->position++; + RE_Compiled tail = re_compile_sequence(re); + re_program_append(&head, re_new_Jump(re, tail.size)); + re_program_prepend(&head, re_new_Fork(re, 0, head.size)); + re_program_append(&head, tail); + } + return head; +} + +static RE_Compiled re_compile(struct re *re) { + jmp_buf env; + RE_Compiled insns = RE_COMPILED_INITIALISER; + re->error_env = &env; + if (setjmp(env)) { /* syntax error */ + return insns; + } + insns = re_compile_expression(re); + re_array_of(RE_Insn) program = RE_ARRAY_OF_INITIALISER; + RE_Insn *insn, *next; + for (insn = insns.first; insn; insn = next) { + re_array_append(re, program, *insn); + next = insn->next; + RE_FREE(re, insn); + } + +#if 0 + int i; + for (i = 0; i < program.size; ++i) { + RE_Insn *insn = &program.at[i]; + printf("%03i ", i); + switch (insn->opcode) { + case RE_Any: + printf("Any\n"); + break; + case RE_Char: + printf("Char %li\n", insn->x); + break; + case RE_Class: { + printf("Class "); + { + RE_BitSet *c = insn->c; + int i; + putchar('['); + if (c->inverted) { + putchar('^'); + } + for (i = 0; i < 256; ++i) + if (re_bitset__includes(c, i)) { + switch (i) { + case '\a': + printf("\\a"); + break; + case '\b': + printf("\\b"); + break; + case '\t': + printf("\\t"); + break; + case '\n': + printf("\\n"); + break; + case '\v': + printf("\\v"); + break; + case '\f': + printf("\\f"); + break; + case '\r': + printf("\\r"); + break; + case '\\': + printf("\\\\"); + break; + case ']': + printf("\\]"); + break; + case '^': + printf("\\^"); + break; + case '-': + printf("\\-"); + break; + default: + if (isprint(i)) { + putchar(i); + } else { + printf("\\x%02x", i); + } + } + } + putchar(']'); + } + putchar('\n'); + break; + } + case RE_Accept: + printf("Accept\n"); + break; + case RE_Jump: + printf("Jump %li -> %03li\n", insn->x, i + 1 + insn->x); + break; + case RE_Fork: + printf("Fork %li %li -> %03li %03li\n", insn->x, insn->y, i + 1 + insn->x, i + 1 + insn->y); + break; + case RE_Begin: + printf("Begin\n"); + break; + case RE_End: + printf("End\n"); + break; + default: + printf("?%i\n", insn->opcode); + break; + } + } +#endif + + assert(program.size == insns.size); + + insns.first = program.at; + insns.last = insns.first + insns.size; + + return insns; +} + +/* submatch recording */ + +typedef re_array_of(char*) re_array_of_charp; + +struct RE_Submatches; +typedef struct RE_Submatches RE_Submatches; + +struct RE_Submatches { + int refs; + re_array_of_charp beginnings; + re_array_of_charp endings; +}; + +static RE_Submatches *re_submatches_copy(struct re *re, RE_Submatches *orig) { + RE_Submatches *subs = RE_CALLOC(re, 1, sizeof(RE_Submatches)); + if (orig) { + subs->beginnings = (re_array_of_charp) re_array_copy(re, orig->beginnings); // -V522 + subs->endings = (re_array_of_charp) re_array_copy(re, orig->endings); + } + return subs; +} + +static void re_submatches_free(struct re *re, RE_Submatches *subs) { + assert(subs); + assert(!subs->refs); + re_array_release(re, subs->beginnings); + re_array_release(re, subs->endings); + RE_FREE(re, subs); +} + +static inline RE_Submatches *re_submatches_link(RE_Submatches *subs) { + if (subs) { + subs->refs++; + } + return subs; +} + +static inline void re_submatches_unlink(struct re *re, RE_Submatches *subs) { + if (subs && (0 == --(subs->refs))) { + re_submatches_free(re, subs); + } +} + +/* matching */ + +struct RE_Thread; +typedef struct RE_Thread RE_Thread; + +struct RE_Thread { + RE_Insn *pc; + RE_Submatches *submatches; +}; + +struct RE_ThreadList; +typedef struct RE_ThreadList RE_ThreadList; + +struct RE_ThreadList { + int size; + RE_Thread *at; +}; + +static inline RE_Thread re_thread(RE_Insn *pc, RE_Submatches *subs) { + return (RE_Thread) { + pc, subs + }; +} + +static void re_thread_schedule(struct re *re, RE_ThreadList *threads, RE_Insn *pc, char *sp, RE_Submatches *subs) { + if (pc->stamp == sp) { + return; + } + pc->stamp = sp; + + switch (pc->opcode) { + case RE_Jump: + re_thread_schedule(re, threads, pc + 1 + pc->x, sp, subs); + return; + case RE_Fork: + re_thread_schedule(re, threads, pc + 1 + pc->x, sp, subs); + re_thread_schedule(re, threads, pc + 1 + pc->y, sp, subs); + return; + case RE_Begin: + subs = re_submatches_copy(re, subs); + re_array_append(re, subs->beginnings, sp); // -V522 + re_thread_schedule(re, threads, pc + 1, sp, subs); + if (!subs->refs) { + re_submatches_free(re, subs); + } + return; + case RE_End: { + subs = re_submatches_copy(re, subs); +# if 0 /* non-nesting groups: ab{cd{ef}gh}ij => {cdef} {efgh} */ + re_array_append(re, subs->endings, sp); +# else /* nesting groups: ab{cd{ef}gh}ij => {cdefgh} {ef} */ + while (subs->endings.size < subs->beginnings.size) re_array_append(re, subs->endings, 0); + int i; + for (i = subs->endings.size; i--; ) { + if (!subs->endings.at[i]) { + subs->endings.at[i] = sp; + break; + } + } +# endif + re_thread_schedule(re, threads, pc + 1, sp, subs); + if (!subs->refs) { + re_submatches_free(re, subs); + } + return; + } + } + threads->at[threads->size++] = re_thread(pc, re_submatches_link(subs)); +} + +static int re_program_run(struct re *re, char *input, char ***saved, int *nsaved) { + int matched = RE_ERROR_NOMATCH; + if (!re) { + return matched; + } + + RE_Submatches *submatches = 0; + RE_ThreadList a = { 0, 0 }, b = { 0, 0 }, *here = &a, *next = &b; + + char *sp = input; + re->position = 0; + + jmp_buf env; + re->error_env = &env; + + if (setjmp(env)) { /* out of memory */ + matched = re->error_code; + goto bailout; + } + + a.at = RE_CALLOC(re, re->code.size, sizeof(RE_Thread)); + b.at = RE_CALLOC(re, re->code.size, sizeof(RE_Thread)); + + re_thread_schedule(re, here, re->code.first, input, 0); + + { + int i; + for (i = 0; i < re->code.size; ++i) + re->code.first[i].stamp = 0; + } + + for (sp = input; here->size; ++sp) { + int i; + for (i = 0; i < here->size; ++i) { + RE_Thread t = here->at[i]; + switch (t.pc->opcode) { + case RE_Any: { + if (*sp) { + re_thread_schedule(re, next, t.pc + 1, sp + 1, t.submatches); + } + break; + } + case RE_Char: { + if (*sp == t.pc->x) { + re_thread_schedule(re, next, t.pc + 1, sp + 1, t.submatches); + } + break; + } + case RE_Class: { + if (re_bitset_includes(t.pc->c, *sp)) { + re_thread_schedule(re, next, t.pc + 1, sp + 1, t.submatches); + } + break; + } + case RE_Accept: { + matched = sp - input; + re_submatches_unlink(re, submatches); + submatches = re_submatches_link(t.submatches); + while (i < here->size) re_submatches_unlink(re, here->at[i++].submatches); + goto nextchar; + } + default: + RE_ERROR(re, ENGINE, "illegal instruction in compiled regular expression (please report this bug)"); + } + re_submatches_unlink(re, t.submatches); + } +nextchar: + ; + RE_ThreadList *tmp = here; + here = next; + next = tmp; + next->size = 0; + if (!*sp) { + break; + } + } + +bailout: + re->position = sp; + + { + int i; + for (i = 0; i < here->size; ++i) + re_submatches_unlink(re, here->at[i].submatches); + } + + RE_FREE(re, a.at); + RE_FREE(re, b.at); + + if (submatches) { + if (saved && nsaved && (matched >= 0)) { + assert(submatches->beginnings.size == submatches->endings.size); + *nsaved = submatches->beginnings.size * 2; + *saved = RE_CALLOC(re, *nsaved, sizeof(char*)); + int i; + for (i = 0; i < *nsaved; i += 2) { + (*saved)[i + 0] = submatches->beginnings.at[i / 2]; + (*saved)[i + 1] = submatches->endings.at[i / 2]; + } + } + re_submatches_unlink(re, submatches); + } + + return matched; +} + +/* public interface */ + +struct re *lwre_new(const char *expr) { + struct re *re = RE_CALLOC(0, 1, sizeof(struct re)); + if (re) { + re->expression = expr; + } + return re; +} + +int lwre_match(struct re *re, char *input) { + RE_FREE(re, re->matches); + re->matches = 0; + re->nmatches = 0; + if (!re->expression) { + return 0; + } + if (!re->code.size) { + re->position = re->expression; + re->error_code = 0; + re->error_message = 0; + re->code = re_compile(re); + if (re->error_code) { + return re->error_code; + } + re->position = 0; + } + return re_program_run(re, input, &re->matches, &re->nmatches); +} + +void lwre_release(struct re *re) { + RE_FREE(re, re->matches); + if (re->code.first) { + re_program_free(re, &re->code); + } + memset(re, 0, sizeof(*re)); +} + +void lwre_reset(struct re *re, const char *expression) { + lwre_release(re); + re->expression = expression; +} + +void lwre_free(struct re *re) { + lwre_release(re); + RE_FREE(0, re); +} + +/* utility */ + +static int re_digit(int c, int base) { + if ((c >= '0') && (c <= '9')) { + c -= '0'; + } else if ((c >= 'A') && (c <= 'Z')) { + c -= ('A' - 10); + } else if ((c >= 'a') && (c <= 'z')) { + c -= ('a' - 10); + } else { + return -1; + } + if (c >= base) { + return -1; + } + return c; +} + +static int re_byte(char **sp, int least, int most, int base, int liberal) { + int c = 0; + char *s = *sp; + while (s - *sp < most) { + int d = re_digit(*s, base); + if (d < 0) { + break; + } + ++s; + c = c * base + d; + } + if (s - *sp < least) { + if (liberal) { + return (*sp)[-1]; + } + --*sp; + return '\\'; + } + *sp = s; + return c; +} + +static int re_log2floor(unsigned int n) { + if (!n) { + return 0; + } + int b = 1; +# define _do(x) if (n >= (1U << x)) (b += x), (n >>= x) + _do(16); + _do(8); + _do(4); + _do(2); + _do(1); +# undef _do + return b; +} + +static void re_escape_utf8(char **sp, unsigned int c) { + char *s = *sp; + if (c < 128) { + *s++ = c; + } else { /* this is good for up to 36 bits of c, which proves that Gordon Bell was right all along */ + int n = re_log2floor((unsigned) c) / 6; + int m = 6 * n; + *s++ = (0xff << (7 - n)) + (c >> m); + while ((m -= 6) >= 0) *s++ = 0x80 + ((c >> m) & 0x3F); + } + *sp = s; +} + +char *lwre_escape(char *s, int liberal) { + char *in = s, *out = s; + int c; + while ((c = *in++)) { + int u = 0; + if ('\\' == c) { + c = *in++; + switch (c) { + case '0': + case '1': + c = re_byte(&in, 1, 3, 8, liberal); + break; + case '\\': + //c = '\\'; + break; + case 'a': + c = '\a'; + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'U': + c = re_byte(&in, 8, 8, 16, liberal); + u = 1; + break; + case 'u': + c = re_byte(&in, 4, 4, 16, liberal); + u = 1; + break; + case 'v': + c = '\v'; + break; + case 'x': + c = re_byte(&in, 1, 2, 16, liberal); + break; + default: { + if (!liberal) { /* pass escape character through unharmed */ + --in; + c = '\\'; + } + break; + } + } + } + if (u) { + re_escape_utf8(&out, c); + } else { + *out++ = c; + } + } + assert(out <= in); + *out = 0; + return s; +} + +/* testing */ + +#ifdef LWRE_TEST + +#include + +/* echo stdin to stout with ANSI terminal escapes to turn every number red */ + +int main(int argc, char **argv) { + static struct re re = RE_INITIALISER("(.*?{[0-9]+})*"); + char buf[1024]; + while (fgets(buf, sizeof(buf), stdin)) { + int n; + if (((n = lwre_match(&re, buf)) < 0) && (RE_ERROR_NOMATCH != n)) { + fprintf(stderr, "%i %ss: %s\n", n, re.error_message, re.position); + break; + } else { + char *p = buf; + int n = 0; + while (*p) { + if ((n < re.nmatches) && (p == re.matches[n])) { + if (n & 1) { + printf("\033[0m"); /* end of match: clear all attributes */ + } else { + printf("\033[1;31m"); /* start of match: bold and foreground red */ + } + ++n; + } + putchar(*p++); + } + } + } + lwre_release(&re); + return 0; +} + +#endif /* REGEXP_TEST */ diff --git a/src/util/lwre.h b/src/util/lwre.h new file mode 100644 index 0000000..3790b47 --- /dev/null +++ b/src/util/lwre.h @@ -0,0 +1,48 @@ +#ifndef __lwre_h_ +#define __lwre_h_ + +#include +#include + +struct RE_Insn; + +struct RE_Compiled { + int size; + struct RE_Insn *first; + struct RE_Insn *last; +}; + +#define RE_COMPILED_INITIALISER { 0, 0, 0 } + +struct re { + const char *expression; + const char *position; + jmp_buf *error_env; + int error_code; + char *error_message; + struct RE_Compiled code; + char **matches; + int nmatches; +#ifdef RE_EXTRA_MEMBERS + RE_MEMBERS +#endif +}; + +#define RE_INITIALISER(EXPR) { (EXPR), 0, 0, 0, 0, RE_COMPILED_INITIALISER, 0, 0 } + +#define RE_ERROR_NONE 0 +#define RE_ERROR_NOMATCH -1 +#define RE_ERROR_NOMEM -2 +#define RE_ERROR_CHARSET -3 +#define RE_ERROR_SUBEXP -4 +#define RE_ERROR_SUBMATCH -5 +#define RE_ERROR_ENGINE -6 + +IW_EXPORT struct re *lwre_new(const char *expression); +IW_EXPORT int lwre_match(struct re *re, char *input); +IW_EXPORT void lwre_release(struct re *re); +IW_EXPORT void lwre_reset(struct re *re, const char *expression); +IW_EXPORT void lwre_free(struct re *re); +IW_EXPORT char *lwre_escape(char *string, int liberal); + +#endif /* __lwre_h_ */ diff --git a/src/util/sort_r.h b/src/util/sort_r.h new file mode 100644 index 0000000..c641887 --- /dev/null +++ b/src/util/sort_r.h @@ -0,0 +1,342 @@ +/* Isaac Turner 29 April 2014 Public Domain */ +#ifndef SORT_R_H_ +#define SORT_R_H_ + +#include +#include + +/* + + sort_r function to be exported. + + Parameters: + base is the array to be sorted + nel is the number of elements in the array + width is the size in bytes of each element of the array + compar is the comparison function + arg is a pointer to be passed to the comparison function + + void sort_r(void *base, size_t nel, size_t width, + int (*compar)(const void *_a, const void *_b, void *_arg), + void *arg); + + */ + +#define _SORT_R_INLINE inline + +#if ( defined __APPLE__ || defined __MACH__ || defined __DARWIN__ \ + || defined __FreeBSD__ || defined __DragonFly__) && defined JB_HAVE_QSORT_R +# define _SORT_R_BSD +#elif ( defined _GNU_SOURCE || defined __gnu_hurd__ || defined __GNU__ \ + || defined __linux__ || defined __MINGW32__ || defined __GLIBC__) && defined JB_HAVE_QSORT_R +# define _SORT_R_LINUX +#elif (defined _WIN32 || defined _WIN64 || defined __WINDOWS__) +# define _SORT_R_WINDOWS +# undef _SORT_R_INLINE +# define _SORT_R_INLINE __inline +#else +/* Using our own recursive quicksort sort_r_simple() */ +#endif + +#if (defined NESTED_QSORT && NESTED_QSORT == 0) +# undef NESTED_QSORT +#endif + +#define SORT_R_SWAP(a, b, tmp) ((tmp) = (a), (a) = (b), (b) = (tmp)) + +/* swap a and b */ + +/* a and b must not be equal! */ +static _SORT_R_INLINE void sort_r_swap( + char* __restrict a, char* __restrict b, + size_t w) { + char tmp, *end = a + w; + for ( ; a < end; a++, b++) { + SORT_R_SWAP(*a, *b, tmp); + } +} + +/* swap a, b iff a>b */ + +/* a and b must not be equal! */ + +/* __restrict is same as restrict but better support on old machines */ +static _SORT_R_INLINE int sort_r_cmpswap( + char* __restrict a, + char* __restrict b, size_t w, + int (*compar)(const void *_a, + const void *_b, + void *_arg), + void *arg) { + if (compar(a, b, arg) > 0) { + sort_r_swap(a, b, w); + return 1; + } + return 0; +} + +/* + Swap consecutive blocks of bytes of size na and nb starting at memory addr ptr, + with the smallest swap so that the blocks are in the opposite order. Blocks may + be internally re-ordered e.g. + + 12345ab -> ab34512 + 123abc -> abc123 + 12abcde -> deabc12 + */ +static _SORT_R_INLINE void sort_r_swap_blocks(char *ptr, size_t na, size_t nb) { + if ((na > 0) && (nb > 0)) { + if (na > nb) { + sort_r_swap(ptr, ptr + na, nb); + } else { + sort_r_swap(ptr, ptr + nb, na); + } + } +} + +/* Implement recursive quicksort ourselves */ + +/* Note: quicksort is not stable, equivalent values may be swapped */ +static _SORT_R_INLINE void sort_r_simple( + void *base, size_t nel, size_t w, + int (*compar)(const void *_a, + const void *_b, + void *_arg), + void *arg) { + char *b = (char*) base, *end = b + nel * w; + + /* for(size_t i=0; i b && sort_r_cmpswap(pj - w, pj, w, compar, arg); pj -= w) { + } + } + } else { + /* nel > 6; Quicksort */ + + int cmp; + char *pl, *ple, *pr, *pre, *pivot; + char *last = b + w * (nel - 1), *tmp; + + /* + Use median of second, middle and second-last items as pivot. + First and last may have been swapped with pivot and therefore be extreme + */ + char *l[3]; + l[0] = b + w; + l[1] = b + w * (nel / 2); + l[2] = last - w; + + /* printf("pivots: %i, %i, %i\n", *(int*)l[0], *(int*)l[1], *(int*)l[2]); */ + + if (compar(l[0], l[1], arg) > 0) { + SORT_R_SWAP(l[0], l[1], tmp); + } + if (compar(l[1], l[2], arg) > 0) { + SORT_R_SWAP(l[1], l[2], tmp); + if (compar(l[0], l[1], arg) > 0) { + SORT_R_SWAP(l[0], l[1], tmp); + } + } + + /* swap mid value (l[1]), and last element to put pivot as last element */ + if (l[1] != last) { + sort_r_swap(l[1], last, w); + } + + /* + pl is the next item on the left to be compared to the pivot + pr is the last item on the right that was compared to the pivot + ple is the left position to put the next item that equals the pivot + ple is the last right position where we put an item that equals the pivot + + v- end (beyond the array) + EEEEEELLLLLLLLuuuuuuuuGGGGGGGEEEEEEEE. + ^- b ^- ple ^- pl ^- pr ^- pre ^- last (where the pivot is) + + Pivot comparison key: + E = equal, L = less than, u = unknown, G = greater than, E = equal + */ + pivot = last; + ple = pl = b; + pre = pr = last; + + /* + Strategy: + Loop into the list from the left and right at the same time to find: + - an item on the left that is greater than the pivot + - an item on the right that is less than the pivot + Once found, they are swapped and the loop continues. + Meanwhile items that are equal to the pivot are moved to the edges of the + array. + */ + while (pl < pr) { + /* Move left hand items which are equal to the pivot to the far left. + break when we find an item that is greater than the pivot */ + for ( ; pl < pr; pl += w) { + cmp = compar(pl, pivot, arg); + if (cmp > 0) { + break; + } else if (cmp == 0) { + if (ple < pl) { + sort_r_swap(ple, pl, w); + } + ple += w; + } + } + /* break if last batch of left hand items were equal to pivot */ + if (pl >= pr) { + break; + } + /* Move right hand items which are equal to the pivot to the far right. + break when we find an item that is less than the pivot */ + for ( ; pl < pr; ) { + pr -= w; /* Move right pointer onto an unprocessed item */ + cmp = compar(pr, pivot, arg); + if (cmp == 0) { + pre -= w; + if (pr < pre) { + sort_r_swap(pr, pre, w); + } + } else if (cmp < 0) { + if (pl < pr) { + sort_r_swap(pl, pr, w); + } + pl += w; + break; + } + } + } + + pl = pr; /* pr may have gone below pl */ + + /* + Now we need to go from: EEELLLGGGGEEEE + to: LLLEEEEEEEGGGG + + Pivot comparison key: + E = equal, L = less than, u = unknown, G = greater than, E = equal + */ + sort_r_swap_blocks(b, ple - b, pl - ple); + sort_r_swap_blocks(pr, pre - pr, end - pre); + + /*for(size_t i=0; icompar)(a, b, ss->arg); +} + + #endif + + #if defined _SORT_R_LINUX + +typedef int (*__compar_d_fn_t)(const void*, const void*, void*); +extern void qsort_r( + void *base, size_t nel, size_t width, + __compar_d_fn_t __compar, void *arg) +__attribute__((nonnull(1, 4))); + + #endif + +/* implementation */ + +static _SORT_R_INLINE void sort_r( + void *base, size_t nel, size_t width, + int (*compar)(const void *_a, + const void *_b, void *_arg), + void *arg) { + #if defined _SORT_R_LINUX + + #if defined __GLIBC__ && ((__GLIBC__ < 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 8)) + + /* no qsort_r in glibc before 2.8, need to use nested qsort */ + sort_r_simple(base, nel, width, compar, arg); + + #else + + qsort_r(base, nel, width, compar, arg); + + #endif + + #elif defined _SORT_R_BSD + + struct sort_r_data tmp; + tmp.arg = arg; + tmp.compar = compar; + qsort_r(base, nel, width, &tmp, sort_r_arg_swap); + + #elif defined _SORT_R_WINDOWS + + struct sort_r_data tmp; + tmp.arg = arg; + tmp.compar = compar; + qsort_s(base, nel, width, sort_r_arg_swap, &tmp); + + #else + + /* Fall back to our own quicksort implementation */ + sort_r_simple(base, nel, width, compar, arg); + + #endif +} + +#endif /* !NESTED_QSORT */ + +#undef _SORT_R_INLINE +#undef _SORT_R_WINDOWS +#undef _SORT_R_LINUX +#undef _SORT_R_BSD + +#endif /* SORT_R_H_ */ diff --git a/src/util/utf8proc.c b/src/util/utf8proc.c new file mode 100644 index 0000000..56d2234 --- /dev/null +++ b/src/util/utf8proc.c @@ -0,0 +1,327 @@ +/* -*- mode: c; c-basic-offset: 2; tab-width: 2; indent-tabs-mode: nil -*- */ +/* + * Copyright (c) 2015 Steven G. Johnson, Jiahao Chen, Peter Colberg, Tony Kelman, Scott P. Jones, and other + * contributors. + * Copyright (c) 2009 Public Software Group e. V., Berlin, Germany + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* + * This library contains derived data from a modified version of the + * Unicode data files. + * + * The original data files are available at + * http://www.unicode.org/Public/UNIDATA/ + * + * Please notice the copyright statement in the file "utf8proc_data.c". + */ + + +/* + * File name: utf8proc.c + * + * Description: + * Implementation of libutf8proc. + */ + + +#include "utf8proc.h" + +UTF8PROC_DLLEXPORT const utf8proc_int8_t utf8proc_utf8class[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +#define UTF8PROC_HANGUL_SBASE 0xAC00 +#define UTF8PROC_HANGUL_LBASE 0x1100 +#define UTF8PROC_HANGUL_VBASE 0x1161 +#define UTF8PROC_HANGUL_TBASE 0x11A7 +#define UTF8PROC_HANGUL_LCOUNT 19 +#define UTF8PROC_HANGUL_VCOUNT 21 +#define UTF8PROC_HANGUL_TCOUNT 28 +#define UTF8PROC_HANGUL_NCOUNT 588 +#define UTF8PROC_HANGUL_SCOUNT 11172 +/* END is exclusive */ +#define UTF8PROC_HANGUL_L_START 0x1100 +#define UTF8PROC_HANGUL_L_END 0x115A +#define UTF8PROC_HANGUL_L_FILLER 0x115F +#define UTF8PROC_HANGUL_V_START 0x1160 +#define UTF8PROC_HANGUL_V_END 0x11A3 +#define UTF8PROC_HANGUL_T_START 0x11A8 +#define UTF8PROC_HANGUL_T_END 0x11FA +#define UTF8PROC_HANGUL_S_START 0xAC00 +#define UTF8PROC_HANGUL_S_END 0xD7A4 + +/* Should follow semantic-versioning rules (semver.org) based on API + compatibility. (Note that the shared-library version number will + be different, being based on ABI compatibility.): */ +#define STRINGIZEx(x) #x +#define STRINGIZE(x) STRINGIZEx(x) + +UTF8PROC_DLLEXPORT const char *utf8proc_version(void) { + return STRINGIZE(UTF8PROC_VERSION_MAJOR) "." STRINGIZE(UTF8PROC_VERSION_MINOR) "." STRINGIZE(UTF8PROC_VERSION_PATCH) + ""; +} + +UTF8PROC_DLLEXPORT const char *utf8proc_errmsg(utf8proc_ssize_t errcode) { + switch (errcode) { + case UTF8PROC_ERROR_NOMEM: + return "Memory for processing UTF-8 data could not be allocated."; + case UTF8PROC_ERROR_OVERFLOW: + return "UTF-8 string is too long to be processed."; + case UTF8PROC_ERROR_INVALIDUTF8: + return "Invalid UTF-8 string"; + case UTF8PROC_ERROR_NOTASSIGNED: + return "Unassigned Unicode code point found in UTF-8 string."; + case UTF8PROC_ERROR_INVALIDOPTS: + return "Invalid options for UTF-8 processing chosen."; + default: + return "An unknown error occurred while processing UTF-8 data."; + } +} + +#define utf_cont(ch) (((ch) & 0xc0) == 0x80) + +UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_iterate( + const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, utf8proc_int32_t *dst + ) { + utf8proc_uint32_t uc; + const utf8proc_uint8_t *end; + + *dst = -1; + if (!strlen) { + return 0; + } + end = str + ((strlen < 0) ? 4 : strlen); + uc = *str++; + if (uc < 0x80) { + *dst = uc; + return 1; + } + // Must be between 0xc2 and 0xf4 inclusive to be valid + if ((uc - 0xc2) > (0xf4 - 0xc2)) { + return UTF8PROC_ERROR_INVALIDUTF8; + } + if (uc < 0xe0) { // 2-byte sequence + // Must have valid continuation character + if ((str >= end) || !utf_cont(*str)) { + return UTF8PROC_ERROR_INVALIDUTF8; + } + *dst = ((uc & 0x1f) << 6) | (*str & 0x3f); + return 2; + } + if (uc < 0xf0) { // 3-byte sequence + if ((str + 1 >= end) || !utf_cont(*str) || !utf_cont(str[1])) { + return UTF8PROC_ERROR_INVALIDUTF8; + } + // Check for surrogate chars + if ((uc == 0xed) && (*str > 0x9f) ) { + return UTF8PROC_ERROR_INVALIDUTF8; + } + uc = ((uc & 0xf) << 12) | ((*str & 0x3f) << 6) | (str[1] & 0x3f); + if (uc < 0x800) { + return UTF8PROC_ERROR_INVALIDUTF8; + } + *dst = uc; + return 3; + } + // 4-byte sequence + // Must have 3 valid continuation characters + if ((str + 2 >= end) || !utf_cont(*str) || !utf_cont(str[1]) || !utf_cont(str[2])) { + return UTF8PROC_ERROR_INVALIDUTF8; + } + // Make sure in correct range (0x10000 - 0x10ffff) + if (uc == 0xf0) { + if (*str < 0x90) { + return UTF8PROC_ERROR_INVALIDUTF8; + } + } else if (uc == 0xf4) { + if (*str > 0x8f) { + return UTF8PROC_ERROR_INVALIDUTF8; + } + } + *dst = ((uc & 7) << 18) | ((*str & 0x3f) << 12) | ((str[1] & 0x3f) << 6) | (str[2] & 0x3f); + return 4; +} + +UTF8PROC_DLLEXPORT utf8proc_bool utf8proc_codepoint_valid(utf8proc_int32_t uc) { + return (((utf8proc_uint32_t) uc) - 0xd800 > 0x07ff) && ((utf8proc_uint32_t) uc < 0x110000); +} + +UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_encode_char(utf8proc_int32_t uc, utf8proc_uint8_t *dst) { + if (uc < 0x00) { + return 0; + } else if (uc < 0x80) { + dst[0] = (utf8proc_uint8_t) uc; + return 1; + } else if (uc < 0x800) { + dst[0] = (utf8proc_uint8_t) (0xC0 + (uc >> 6)); + dst[1] = (utf8proc_uint8_t) (0x80 + (uc & 0x3F)); + return 2; + // Note: we allow encoding 0xd800-0xdfff here, so as not to change + // the API, however, these are actually invalid in UTF-8 + } else if (uc < 0x10000) { + dst[0] = (utf8proc_uint8_t) (0xE0 + (uc >> 12)); + dst[1] = (utf8proc_uint8_t) (0x80 + ((uc >> 6) & 0x3F)); + dst[2] = (utf8proc_uint8_t) (0x80 + (uc & 0x3F)); + return 3; + } else if (uc < 0x110000) { + dst[0] = (utf8proc_uint8_t) (0xF0 + (uc >> 18)); + dst[1] = (utf8proc_uint8_t) (0x80 + ((uc >> 12) & 0x3F)); + dst[2] = (utf8proc_uint8_t) (0x80 + ((uc >> 6) & 0x3F)); + dst[3] = (utf8proc_uint8_t) (0x80 + (uc & 0x3F)); + return 4; + } else { + return 0; + } +} + +/* internal "unsafe" version that does not check whether uc is in range */ +static utf8proc_ssize_t unsafe_encode_char(utf8proc_int32_t uc, utf8proc_uint8_t *dst) { + if (uc < 0x00) { + return 0; + } else if (uc < 0x80) { + dst[0] = (utf8proc_uint8_t) uc; + return 1; + } else if (uc < 0x800) { + dst[0] = (utf8proc_uint8_t) (0xC0 + (uc >> 6)); + dst[1] = (utf8proc_uint8_t) (0x80 + (uc & 0x3F)); + return 2; + } else if (uc == 0xFFFF) { + dst[0] = (utf8proc_uint8_t) 0xFF; + return 1; + } else if (uc == 0xFFFE) { + dst[0] = (utf8proc_uint8_t) 0xFE; + return 1; + } else if (uc < 0x10000) { + dst[0] = (utf8proc_uint8_t) (0xE0 + (uc >> 12)); + dst[1] = (utf8proc_uint8_t) (0x80 + ((uc >> 6) & 0x3F)); + dst[2] = (utf8proc_uint8_t) (0x80 + (uc & 0x3F)); + return 3; + } else if (uc < 0x110000) { + dst[0] = (utf8proc_uint8_t) (0xF0 + (uc >> 18)); + dst[1] = (utf8proc_uint8_t) (0x80 + ((uc >> 12) & 0x3F)); + dst[2] = (utf8proc_uint8_t) (0x80 + ((uc >> 6) & 0x3F)); + dst[3] = (utf8proc_uint8_t) (0x80 + (uc & 0x3F)); + return 4; + } else { + return 0; + } +} + +/* return whether there is a grapheme break between boundclasses lbc and tbc + (according to the definition of extended grapheme clusters) + + Rule numbering refers to TR29 Version 29 (Unicode 9.0.0): + http://www.unicode.org/reports/tr29/tr29-29.html + + CAVEATS: + Please note that evaluation of GB10 (grapheme breaks between emoji zwj sequences) + and GB 12/13 (regional indicator code points) require knowledge of previous characters + and are thus not handled by this function. This may result in an incorrect break before + an E_Modifier class codepoint and an incorrectly missing break between two + REGIONAL_INDICATOR class code points if such support does not exist in the caller. + + See the special support in grapheme_break_extended, for required bookkeeping by the caller. + */ +static utf8proc_bool grapheme_break_simple(int lbc, int tbc) { + return (lbc == UTF8PROC_BOUNDCLASS_START) ? true // GB1 + : ( lbc == UTF8PROC_BOUNDCLASS_CR // GB3 + && tbc == UTF8PROC_BOUNDCLASS_LF) ? false // --- + : (lbc >= UTF8PROC_BOUNDCLASS_CR && lbc <= UTF8PROC_BOUNDCLASS_CONTROL) ? true // GB4 + : (tbc >= UTF8PROC_BOUNDCLASS_CR && tbc <= UTF8PROC_BOUNDCLASS_CONTROL) ? true // GB5 + : ( lbc == UTF8PROC_BOUNDCLASS_L // GB6 + && ( tbc == UTF8PROC_BOUNDCLASS_L // --- + || tbc == UTF8PROC_BOUNDCLASS_V // --- + || tbc == UTF8PROC_BOUNDCLASS_LV // --- + || tbc == UTF8PROC_BOUNDCLASS_LVT)) ? false // --- + : ( ( lbc == UTF8PROC_BOUNDCLASS_LV // GB7 + || lbc == UTF8PROC_BOUNDCLASS_V) // --- + && ( tbc == UTF8PROC_BOUNDCLASS_V // --- + || tbc == UTF8PROC_BOUNDCLASS_T)) ? false // --- + : ( ( lbc == UTF8PROC_BOUNDCLASS_LVT // GB8 + || lbc == UTF8PROC_BOUNDCLASS_T) // --- + && tbc == UTF8PROC_BOUNDCLASS_T) ? false // --- + : ( tbc == UTF8PROC_BOUNDCLASS_EXTEND // GB9 + || tbc == UTF8PROC_BOUNDCLASS_ZWJ // --- + || tbc == UTF8PROC_BOUNDCLASS_SPACINGMARK // GB9a + || lbc == UTF8PROC_BOUNDCLASS_PREPEND) ? false // GB9b + : ( ( lbc == UTF8PROC_BOUNDCLASS_E_BASE // GB10 (requires additional + // handling below) + || lbc == UTF8PROC_BOUNDCLASS_E_BASE_GAZ) // ---- + && tbc == UTF8PROC_BOUNDCLASS_E_MODIFIER) ? false // ---- + : ( lbc == UTF8PROC_BOUNDCLASS_ZWJ // GB11 + && ( tbc == UTF8PROC_BOUNDCLASS_GLUE_AFTER_ZWJ // ---- + || tbc == UTF8PROC_BOUNDCLASS_E_BASE_GAZ)) ? false // ---- + : ( lbc == UTF8PROC_BOUNDCLASS_REGIONAL_INDICATOR // GB12/13 (requires additional + // handling below) + && tbc == UTF8PROC_BOUNDCLASS_REGIONAL_INDICATOR) ? false // ---- + : true; // GB999 +} + +static utf8proc_bool grapheme_break_extended(int lbc, int tbc, utf8proc_int32_t *state) { + int lbc_override = ((state && *state != UTF8PROC_BOUNDCLASS_START) + ? *state : lbc); + utf8proc_bool break_permitted = grapheme_break_simple(lbc_override, tbc); + if (state) { + // Special support for GB 12/13 made possible by GB999. After two RI + // class codepoints we want to force a break. Do this by resetting the + // second RI's bound class to UTF8PROC_BOUNDCLASS_OTHER, to force a break + // after that character according to GB999 (unless of course such a break is + // forbidden by a different rule such as GB9). + if ((*state == tbc) && (tbc == UTF8PROC_BOUNDCLASS_REGIONAL_INDICATOR)) { + *state = UTF8PROC_BOUNDCLASS_OTHER; + } + // Special support for GB10. Fold any EXTEND codepoints into the previous + // boundclass if we're dealing with an emoji base boundclass. + else if ( ( (*state == UTF8PROC_BOUNDCLASS_E_BASE) + || (*state == UTF8PROC_BOUNDCLASS_E_BASE_GAZ) ) + && (tbc == UTF8PROC_BOUNDCLASS_EXTEND) ) { + *state = UTF8PROC_BOUNDCLASS_E_BASE; + } else { + *state = tbc; + } + } + return break_permitted; +} + +static utf8proc_int32_t seqindex_decode_entry(const utf8proc_uint16_t **entry) { + utf8proc_int32_t entry_cp = **entry; + if ((entry_cp & 0xF800) == 0xD800) { + *entry = *entry + 1; + entry_cp = ((entry_cp & 0x03FF) << 10) | (**entry & 0x03FF); + entry_cp += 0x10000; + } + return entry_cp; +} diff --git a/src/util/utf8proc.h b/src/util/utf8proc.h new file mode 100644 index 0000000..e962595 --- /dev/null +++ b/src/util/utf8proc.h @@ -0,0 +1,733 @@ +/* + * Copyright (c) 2015 Steven G. Johnson, Jiahao Chen, Peter Colberg, Tony Kelman, Scott P. Jones, and other + * contributors. + * Copyright (c) 2009 Public Software Group e. V., Berlin, Germany + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + + +/** + * @mainpage + * + * utf8proc is a free/open-source (MIT/expat licensed) C library + * providing Unicode normalization, case-folding, and other operations + * for strings in the UTF-8 encoding, supporting Unicode version + * 9.0.0. See the utf8proc home page (http://julialang.org/utf8proc/) + * for downloads and other information, or the source code on github + * (https://github.com/JuliaLang/utf8proc). + * + * For the utf8proc API documentation, see: @ref utf8proc.h + * + * The features of utf8proc include: + * + * - Transformation of strings (@ref utf8proc_map) to: + * - decompose (@ref UTF8PROC_DECOMPOSE) or compose (@ref UTF8PROC_COMPOSE) Unicode combining characters + *(http://en.wikipedia.org/wiki/Combining_character) + * - canonicalize Unicode compatibility characters (@ref UTF8PROC_COMPAT) + * - strip "ignorable" (@ref UTF8PROC_IGNORE) characters, control characters (@ref UTF8PROC_STRIPCC), or combining + * characters such as accents (@ref UTF8PROC_STRIPMARK) + * - case-folding (@ref UTF8PROC_CASEFOLD) + * - Unicode normalization: @ref utf8proc_NFD, @ref utf8proc_NFC, @ref utf8proc_NFKD, @ref utf8proc_NFKC + * - Detecting grapheme boundaries (@ref utf8proc_grapheme_break and @ref UTF8PROC_CHARBOUND) + * - Character-width computation: @ref utf8proc_charwidth + * - Classification of characters by Unicode category: @ref utf8proc_category and @ref utf8proc_category_string + * - Encode (@ref utf8proc_encode_char) and decode (@ref utf8proc_iterate) Unicode codepoints to/from UTF-8. + */ + +/** @file */ + +#ifndef UTF8PROC_H +#define UTF8PROC_H + +/** @name API version + * + * The utf8proc API version MAJOR.MINOR.PATCH, following + * semantic-versioning rules (http://semver.org) based on API + * compatibility. + * + * This is also returned at runtime by @ref utf8proc_version; however, the + * runtime version may append a string like "-dev" to the version number + * for prerelease versions. + * + * @note The shared-library version number in the Makefile + * (and CMakeLists.txt, and MANIFEST) may be different, + * being based on ABI compatibility rather than API compatibility. + */ +/** @{ */ +/** The MAJOR version number (increased when backwards API compatibility is broken). */ +#define UTF8PROC_VERSION_MAJOR 2 +/** The MINOR version number (increased when new functionality is added in a backwards-compatible manner). */ +#define UTF8PROC_VERSION_MINOR 2 +/** The PATCH version (increased for fixes that do not change the API). */ +#define UTF8PROC_VERSION_PATCH 0 +/** @} */ + +#include + +#if defined(_MSC_VER) && _MSC_VER < 1800 +// MSVC prior to 2013 lacked stdbool.h and inttypes.h +typedef signed char utf8proc_int8_t; +typedef unsigned char utf8proc_uint8_t; +typedef short utf8proc_int16_t; +typedef unsigned short utf8proc_uint16_t; +typedef int utf8proc_int32_t; +typedef unsigned int utf8proc_uint32_t; +# ifdef _WIN64 +typedef __int64 utf8proc_ssize_t; +typedef unsigned __int64 utf8proc_size_t; +# else +typedef int utf8proc_ssize_t; +typedef unsigned int utf8proc_size_t; +# endif +# ifndef __cplusplus +// emulate C99 bool +typedef unsigned char utf8proc_bool; +# ifndef __bool_true_false_are_defined +# define false 0 +# define true 1 +# define __bool_true_false_are_defined 1 +# endif +# else +typedef bool utf8proc_bool; +# endif +#else +# include +# include +# include +typedef int8_t utf8proc_int8_t; +typedef uint8_t utf8proc_uint8_t; +typedef int16_t utf8proc_int16_t; +typedef uint16_t utf8proc_uint16_t; +typedef int32_t utf8proc_int32_t; +typedef uint32_t utf8proc_uint32_t; +typedef size_t utf8proc_size_t; +typedef ptrdiff_t utf8proc_ssize_t; +typedef bool utf8proc_bool; +#endif +#include + +#define UTF8PROC_STATIC + +#ifdef UTF8PROC_STATIC +# define UTF8PROC_DLLEXPORT +#else +# ifdef _WIN32 +# ifdef UTF8PROC_EXPORTS +# define UTF8PROC_DLLEXPORT __declspec(dllexport) +# else +# define UTF8PROC_DLLEXPORT __declspec(dllimport) +# endif +# elif __GNUC__ >= 4 +# define UTF8PROC_DLLEXPORT __attribute__((visibility("default"))) +# else +# define UTF8PROC_DLLEXPORT +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef SSIZE_MAX +#define SSIZE_MAX ((size_t) SIZE_MAX / 2) +#endif + +#ifndef UINT16_MAX +# define UINT16_MAX 65535U +#endif + +/** + * Option flags used by several functions in the library. + */ +typedef enum { + /** The given UTF-8 input is NULL terminated. */ + UTF8PROC_NULLTERM = (1 << 0), + /** Unicode Versioning Stability has to be respected. */ + UTF8PROC_STABLE = (1 << 1), + /** Compatibility decomposition (i.e. formatting information is lost). */ + UTF8PROC_COMPAT = (1 << 2), + /** Return a result with decomposed characters. */ + UTF8PROC_COMPOSE = (1 << 3), + /** Return a result with decomposed characters. */ + UTF8PROC_DECOMPOSE = (1 << 4), + /** Strip "default ignorable characters" such as SOFT-HYPHEN or ZERO-WIDTH-SPACE. */ + UTF8PROC_IGNORE = (1 << 5), + /** Return an error, if the input contains unassigned codepoints. */ + UTF8PROC_REJECTNA = (1 << 6), + /** + * Indicating that NLF-sequences (LF, CRLF, CR, NEL) are representing a + * line break, and should be converted to the codepoint for line + * separation (LS). + */ + UTF8PROC_NLF2LS = (1 << 7), + /** + * Indicating that NLF-sequences are representing a paragraph break, and + * should be converted to the codepoint for paragraph separation + * (PS). + */ + UTF8PROC_NLF2PS = (1 << 8), + /** Indicating that the meaning of NLF-sequences is unknown. */ + UTF8PROC_NLF2LF = (UTF8PROC_NLF2LS | UTF8PROC_NLF2PS), + /** Strips and/or convers control characters. + * + * NLF-sequences are transformed into space, except if one of the + * NLF2LS/PS/LF options is given. HorizontalTab (HT) and FormFeed (FF) + * are treated as a NLF-sequence in this case. All other control + * characters are simply removed. + */ + UTF8PROC_STRIPCC = (1 << 9), + /** + * Performs unicode case folding, to be able to do a case-insensitive + * string comparison. + */ + UTF8PROC_CASEFOLD = (1 << 10), + /** + * Inserts 0xFF bytes at the beginning of each sequence which is + * representing a single grapheme cluster (see UAX#29). + */ + UTF8PROC_CHARBOUND = (1 << 11), + /** Lumps certain characters together. + * + * E.g. HYPHEN U+2010 and MINUS U+2212 to ASCII "-". See lump.md for details. + * + * If NLF2LF is set, this includes a transformation of paragraph and + * line separators to ASCII line-feed (LF). + */ + UTF8PROC_LUMP = (1 << 12), + /** Strips all character markings. + * + * This includes non-spacing, spacing and enclosing (i.e. accents). + * @note This option works only with @ref UTF8PROC_COMPOSE or + * @ref UTF8PROC_DECOMPOSE + */ + UTF8PROC_STRIPMARK = (1 << 13), + /** + * Strip unassigned codepoints. + */ + UTF8PROC_STRIPNA = (1 << 14), +} utf8proc_option_t; + +/** @name Error codes + * Error codes being returned by almost all functions. + */ +/** @{ */ +/** Memory could not be allocated. */ +#define UTF8PROC_ERROR_NOMEM -1 +/** The given string is too long to be processed. */ +#define UTF8PROC_ERROR_OVERFLOW -2 +/** The given string is not a legal UTF-8 string. */ +#define UTF8PROC_ERROR_INVALIDUTF8 -3 +/** The @ref UTF8PROC_REJECTNA flag was set and an unassigned codepoint was found. */ +#define UTF8PROC_ERROR_NOTASSIGNED -4 +/** Invalid options have been used. */ +#define UTF8PROC_ERROR_INVALIDOPTS -5 +/** @} */ + +/* @name Types */ + +/** Holds the value of a property. */ +typedef utf8proc_int16_t utf8proc_propval_t; + +/** Struct containing information about a codepoint. */ +typedef struct utf8proc_property_struct { + /** + * Unicode category. + * @see utf8proc_category_t. + */ + utf8proc_propval_t category; + utf8proc_propval_t combining_class; + /** + * Bidirectional class. + * @see utf8proc_bidi_class_t. + */ + utf8proc_propval_t bidi_class; + /** + * @anchor Decomposition type. + * @see utf8proc_decomp_type_t. + */ + utf8proc_propval_t decomp_type; + utf8proc_uint16_t decomp_seqindex; + utf8proc_uint16_t casefold_seqindex; + utf8proc_uint16_t uppercase_seqindex; + utf8proc_uint16_t lowercase_seqindex; + utf8proc_uint16_t titlecase_seqindex; + utf8proc_uint16_t comb_index; + unsigned bidi_mirrored : 1; + unsigned comp_exclusion : 1; + /** + * Can this codepoint be ignored? + * + * Used by @ref utf8proc_decompose_char when @ref UTF8PROC_IGNORE is + * passed as an option. + */ + unsigned ignorable : 1; + unsigned control_boundary : 1; + /** The width of the codepoint. */ + unsigned charwidth : 2; + unsigned pad : 2; + /** + * Boundclass. + * @see utf8proc_boundclass_t. + */ + unsigned boundclass : 8; +} utf8proc_property_t; + +/** Unicode categories. */ +typedef enum { + UTF8PROC_CATEGORY_CN = 0, /**< Other, not assigned */ + UTF8PROC_CATEGORY_LU = 1, /**< Letter, uppercase */ + UTF8PROC_CATEGORY_LL = 2, /**< Letter, lowercase */ + UTF8PROC_CATEGORY_LT = 3, /**< Letter, titlecase */ + UTF8PROC_CATEGORY_LM = 4, /**< Letter, modifier */ + UTF8PROC_CATEGORY_LO = 5, /**< Letter, other */ + UTF8PROC_CATEGORY_MN = 6, /**< Mark, nonspacing */ + UTF8PROC_CATEGORY_MC = 7, /**< Mark, spacing combining */ + UTF8PROC_CATEGORY_ME = 8, /**< Mark, enclosing */ + UTF8PROC_CATEGORY_ND = 9, /**< Number, decimal digit */ + UTF8PROC_CATEGORY_NL = 10, /**< Number, letter */ + UTF8PROC_CATEGORY_NO = 11, /**< Number, other */ + UTF8PROC_CATEGORY_PC = 12, /**< Punctuation, connector */ + UTF8PROC_CATEGORY_PD = 13, /**< Punctuation, dash */ + UTF8PROC_CATEGORY_PS = 14, /**< Punctuation, open */ + UTF8PROC_CATEGORY_PE = 15, /**< Punctuation, close */ + UTF8PROC_CATEGORY_PI = 16, /**< Punctuation, initial quote */ + UTF8PROC_CATEGORY_PF = 17, /**< Punctuation, final quote */ + UTF8PROC_CATEGORY_PO = 18, /**< Punctuation, other */ + UTF8PROC_CATEGORY_SM = 19, /**< Symbol, math */ + UTF8PROC_CATEGORY_SC = 20, /**< Symbol, currency */ + UTF8PROC_CATEGORY_SK = 21, /**< Symbol, modifier */ + UTF8PROC_CATEGORY_SO = 22, /**< Symbol, other */ + UTF8PROC_CATEGORY_ZS = 23, /**< Separator, space */ + UTF8PROC_CATEGORY_ZL = 24, /**< Separator, line */ + UTF8PROC_CATEGORY_ZP = 25, /**< Separator, paragraph */ + UTF8PROC_CATEGORY_CC = 26, /**< Other, control */ + UTF8PROC_CATEGORY_CF = 27, /**< Other, format */ + UTF8PROC_CATEGORY_CS = 28, /**< Other, surrogate */ + UTF8PROC_CATEGORY_CO = 29, /**< Other, private use */ +} utf8proc_category_t; + +/** Bidirectional character classes. */ +typedef enum { + UTF8PROC_BIDI_CLASS_L = 1, /**< Left-to-Right */ + UTF8PROC_BIDI_CLASS_LRE = 2, /**< Left-to-Right Embedding */ + UTF8PROC_BIDI_CLASS_LRO = 3, /**< Left-to-Right Override */ + UTF8PROC_BIDI_CLASS_R = 4, /**< Right-to-Left */ + UTF8PROC_BIDI_CLASS_AL = 5, /**< Right-to-Left Arabic */ + UTF8PROC_BIDI_CLASS_RLE = 6, /**< Right-to-Left Embedding */ + UTF8PROC_BIDI_CLASS_RLO = 7, /**< Right-to-Left Override */ + UTF8PROC_BIDI_CLASS_PDF = 8, /**< Pop Directional Format */ + UTF8PROC_BIDI_CLASS_EN = 9, /**< European Number */ + UTF8PROC_BIDI_CLASS_ES = 10, /**< European Separator */ + UTF8PROC_BIDI_CLASS_ET = 11, /**< European Number Terminator */ + UTF8PROC_BIDI_CLASS_AN = 12, /**< Arabic Number */ + UTF8PROC_BIDI_CLASS_CS = 13, /**< Common Number Separator */ + UTF8PROC_BIDI_CLASS_NSM = 14, /**< Nonspacing Mark */ + UTF8PROC_BIDI_CLASS_BN = 15, /**< Boundary Neutral */ + UTF8PROC_BIDI_CLASS_B = 16, /**< Paragraph Separator */ + UTF8PROC_BIDI_CLASS_S = 17, /**< Segment Separator */ + UTF8PROC_BIDI_CLASS_WS = 18, /**< Whitespace */ + UTF8PROC_BIDI_CLASS_ON = 19, /**< Other Neutrals */ + UTF8PROC_BIDI_CLASS_LRI = 20, /**< Left-to-Right Isolate */ + UTF8PROC_BIDI_CLASS_RLI = 21, /**< Right-to-Left Isolate */ + UTF8PROC_BIDI_CLASS_FSI = 22, /**< First Strong Isolate */ + UTF8PROC_BIDI_CLASS_PDI = 23, /**< Pop Directional Isolate */ +} utf8proc_bidi_class_t; + +/** Decomposition type. */ +typedef enum { + UTF8PROC_DECOMP_TYPE_FONT = 1, /**< Font */ + UTF8PROC_DECOMP_TYPE_NOBREAK = 2, /**< Nobreak */ + UTF8PROC_DECOMP_TYPE_INITIAL = 3, /**< Initial */ + UTF8PROC_DECOMP_TYPE_MEDIAL = 4, /**< Medial */ + UTF8PROC_DECOMP_TYPE_FINAL = 5, /**< Final */ + UTF8PROC_DECOMP_TYPE_ISOLATED = 6, /**< Isolated */ + UTF8PROC_DECOMP_TYPE_CIRCLE = 7, /**< Circle */ + UTF8PROC_DECOMP_TYPE_SUPER = 8, /**< Super */ + UTF8PROC_DECOMP_TYPE_SUB = 9, /**< Sub */ + UTF8PROC_DECOMP_TYPE_VERTICAL = 10, /**< Vertical */ + UTF8PROC_DECOMP_TYPE_WIDE = 11, /**< Wide */ + UTF8PROC_DECOMP_TYPE_NARROW = 12, /**< Narrow */ + UTF8PROC_DECOMP_TYPE_SMALL = 13, /**< Small */ + UTF8PROC_DECOMP_TYPE_SQUARE = 14, /**< Square */ + UTF8PROC_DECOMP_TYPE_FRACTION = 15, /**< Fraction */ + UTF8PROC_DECOMP_TYPE_COMPAT = 16, /**< Compat */ +} utf8proc_decomp_type_t; + +/** Boundclass property. (TR29) */ +typedef enum { + UTF8PROC_BOUNDCLASS_START = 0, /**< Start */ + UTF8PROC_BOUNDCLASS_OTHER = 1, /**< Other */ + UTF8PROC_BOUNDCLASS_CR = 2, /**< Cr */ + UTF8PROC_BOUNDCLASS_LF = 3, /**< Lf */ + UTF8PROC_BOUNDCLASS_CONTROL = 4, /**< Control */ + UTF8PROC_BOUNDCLASS_EXTEND = 5, /**< Extend */ + UTF8PROC_BOUNDCLASS_L = 6, /**< L */ + UTF8PROC_BOUNDCLASS_V = 7, /**< V */ + UTF8PROC_BOUNDCLASS_T = 8, /**< T */ + UTF8PROC_BOUNDCLASS_LV = 9, /**< Lv */ + UTF8PROC_BOUNDCLASS_LVT = 10, /**< Lvt */ + UTF8PROC_BOUNDCLASS_REGIONAL_INDICATOR = 11, /**< Regional indicator */ + UTF8PROC_BOUNDCLASS_SPACINGMARK = 12, /**< Spacingmark */ + UTF8PROC_BOUNDCLASS_PREPEND = 13, /**< Prepend */ + UTF8PROC_BOUNDCLASS_ZWJ = 14, /**< Zero Width Joiner */ + UTF8PROC_BOUNDCLASS_E_BASE = 15, /**< Emoji Base */ + UTF8PROC_BOUNDCLASS_E_MODIFIER = 16, /**< Emoji Modifier */ + UTF8PROC_BOUNDCLASS_GLUE_AFTER_ZWJ = 17, /**< Glue_After_ZWJ */ + UTF8PROC_BOUNDCLASS_E_BASE_GAZ = 18, /**< E_BASE + GLUE_AFTER_ZJW */ +} utf8proc_boundclass_t; + +/** + * Function pointer type passed to @ref utf8proc_map_custom and + * @ref utf8proc_decompose_custom, which is used to specify a user-defined + * mapping of codepoints to be applied in conjunction with other mappings. + */ +typedef utf8proc_int32_t (*utf8proc_custom_func)(utf8proc_int32_t codepoint, void *data); + +/** + * Array containing the byte lengths of a UTF-8 encoded codepoint based + * on the first byte. + */ +UTF8PROC_DLLEXPORT extern const utf8proc_int8_t utf8proc_utf8class[256]; + +/** + * Returns the utf8proc API version as a string MAJOR.MINOR.PATCH + * (http://semver.org format), possibly with a "-dev" suffix for + * development versions. + */ +UTF8PROC_DLLEXPORT const char *utf8proc_version(void); + +/** + * Returns an informative error string for the given utf8proc error code + * (e.g. the error codes returned by @ref utf8proc_map). + */ +UTF8PROC_DLLEXPORT const char *utf8proc_errmsg(utf8proc_ssize_t errcode); + +/** + * Reads a single codepoint from the UTF-8 sequence being pointed to by `str`. + * The maximum number of bytes read is `strlen`, unless `strlen` is + * negative (in which case up to 4 bytes are read). + * + * If a valid codepoint could be read, it is stored in the variable + * pointed to by `codepoint_ref`, otherwise that variable will be set to -1. + * In case of success, the number of bytes read is returned; otherwise, a + * negative error code is returned. + */ +UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_iterate( + const utf8proc_uint8_t *str, + utf8proc_ssize_t strlen, + utf8proc_int32_t *codepoint_ref); + +/** + * Check if a codepoint is valid (regardless of whether it has been + * assigned a value by the current Unicode standard). + * + * @return 1 if the given `codepoint` is valid and otherwise return 0. + */ +UTF8PROC_DLLEXPORT utf8proc_bool utf8proc_codepoint_valid(utf8proc_int32_t codepoint); + +/** + * Encodes the codepoint as an UTF-8 string in the byte array pointed + * to by `dst`. This array must be at least 4 bytes long. + * + * In case of success the number of bytes written is returned, and + * otherwise 0 is returned. + * + * This function does not check whether `codepoint` is valid Unicode. + */ +UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_encode_char(utf8proc_int32_t codepoint, utf8proc_uint8_t *dst); + +/** + * Look up the properties for a given codepoint. + * + * @param codepoint The Unicode codepoint. + * + * @returns + * A pointer to a (constant) struct containing information about + * the codepoint. + * @par + * If the codepoint is unassigned or invalid, a pointer to a special struct is + * returned in which `category` is 0 (@ref UTF8PROC_CATEGORY_CN). + */ +UTF8PROC_DLLEXPORT const utf8proc_property_t *utf8proc_get_property(utf8proc_int32_t codepoint); + +/** Decompose a codepoint into an array of codepoints. + * + * @param codepoint the codepoint. + * @param dst the destination buffer. + * @param bufsize the size of the destination buffer. + * @param options one or more of the following flags: + * - @ref UTF8PROC_REJECTNA - return an error `codepoint` is unassigned + * - @ref UTF8PROC_IGNORE - strip "default ignorable" codepoints + * - @ref UTF8PROC_CASEFOLD - apply Unicode casefolding + * - @ref UTF8PROC_COMPAT - replace certain codepoints with their + * compatibility decomposition + * - @ref UTF8PROC_CHARBOUND - insert 0xFF bytes before each grapheme cluster + * - @ref UTF8PROC_LUMP - lump certain different codepoints together + * - @ref UTF8PROC_STRIPMARK - remove all character marks + * - @ref UTF8PROC_STRIPNA - remove unassigned codepoints + * @param last_boundclass + * Pointer to an integer variable containing + * the previous codepoint's boundary class if the @ref UTF8PROC_CHARBOUND + * option is used. Otherwise, this parameter is ignored. + * + * @return + * In case of success, the number of codepoints written is returned; in case + * of an error, a negative error code is returned (@ref utf8proc_errmsg). + * @par + * If the number of written codepoints would be bigger than `bufsize`, the + * required buffer size is returned, while the buffer will be overwritten with + * undefined data. + */ +UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_decompose_char( + utf8proc_int32_t codepoint, utf8proc_int32_t *dst, utf8proc_ssize_t bufsize, + utf8proc_option_t options, int *last_boundclass + ); + +/** + * The same as @ref utf8proc_decompose_char, but acts on a whole UTF-8 + * string and orders the decomposed sequences correctly. + * + * If the @ref UTF8PROC_NULLTERM flag in `options` is set, processing + * will be stopped, when a NULL byte is encounted, otherwise `strlen` + * bytes are processed. The result (in the form of 32-bit unicode + * codepoints) is written into the buffer being pointed to by + * `buffer` (which must contain at least `bufsize` entries). In case of + * success, the number of codepoints written is returned; in case of an + * error, a negative error code is returned (@ref utf8proc_errmsg). + * See @ref utf8proc_decompose_custom to supply additional transformations. + * + * If the number of written codepoints would be bigger than `bufsize`, the + * required buffer size is returned, while the buffer will be overwritten with + * undefined data. + */ +UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_decompose( + const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, + utf8proc_int32_t *buffer, utf8proc_ssize_t bufsize, utf8proc_option_t options + ); + +/** + * The same as @ref utf8proc_decompose, but also takes a `custom_func` mapping function + * that is called on each codepoint in `str` before any other transformations + * (along with a `custom_data` pointer that is passed through to `custom_func`). + * The `custom_func` argument is ignored if it is `NULL`. See also @ref utf8proc_map_custom. + */ +UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_decompose_custom( + const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, + utf8proc_int32_t *buffer, utf8proc_ssize_t bufsize, utf8proc_option_t options, + utf8proc_custom_func custom_func, void *custom_data + ); + +/** + * Normalizes the sequence of `length` codepoints pointed to by `buffer` + * in-place (i.e., the result is also stored in `buffer`). + * + * @param buffer the (native-endian UTF-32) unicode codepoints to re-encode. + * @param length the length (in codepoints) of the buffer. + * @param options a bitwise or (`|`) of one or more of the following flags: + * - @ref UTF8PROC_NLF2LS - convert LF, CRLF, CR and NEL into LS + * - @ref UTF8PROC_NLF2PS - convert LF, CRLF, CR and NEL into PS + * - @ref UTF8PROC_NLF2LF - convert LF, CRLF, CR and NEL into LF + * - @ref UTF8PROC_STRIPCC - strip or convert all non-affected control characters + * - @ref UTF8PROC_COMPOSE - try to combine decomposed codepoints into composite + * codepoints + * - @ref UTF8PROC_STABLE - prohibit combining characters that would violate + * the unicode versioning stability + * + * @return + * In case of success, the length (in codepoints) of the normalized UTF-32 string is + * returned; otherwise, a negative error code is returned (@ref utf8proc_errmsg). + * + * @warning The entries of the array pointed to by `str` have to be in the + * range `0x0000` to `0x10FFFF`. Otherwise, the program might crash! + */ +UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_normalize_utf32( + utf8proc_int32_t *buffer, + utf8proc_ssize_t length, + utf8proc_option_t options); + +/** + * Reencodes the sequence of `length` codepoints pointed to by `buffer` + * UTF-8 data in-place (i.e., the result is also stored in `buffer`). + * Can optionally normalize the UTF-32 sequence prior to UTF-8 conversion. + * + * @param buffer the (native-endian UTF-32) unicode codepoints to re-encode. + * @param length the length (in codepoints) of the buffer. + * @param options a bitwise or (`|`) of one or more of the following flags: + * - @ref UTF8PROC_NLF2LS - convert LF, CRLF, CR and NEL into LS + * - @ref UTF8PROC_NLF2PS - convert LF, CRLF, CR and NEL into PS + * - @ref UTF8PROC_NLF2LF - convert LF, CRLF, CR and NEL into LF + * - @ref UTF8PROC_STRIPCC - strip or convert all non-affected control characters + * - @ref UTF8PROC_COMPOSE - try to combine decomposed codepoints into composite + * codepoints + * - @ref UTF8PROC_STABLE - prohibit combining characters that would violate + * the unicode versioning stability + * - @ref UTF8PROC_CHARBOUND - insert 0xFF bytes before each grapheme cluster + * + * @return + * In case of success, the length (in bytes) of the resulting nul-terminated + * UTF-8 string is returned; otherwise, a negative error code is returned + * (@ref utf8proc_errmsg). + * + * @warning The amount of free space pointed to by `buffer` must + * exceed the amount of the input data by one byte, and the + * entries of the array pointed to by `str` have to be in the + * range `0x0000` to `0x10FFFF`. Otherwise, the program might crash! + */ +UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_reencode( + utf8proc_int32_t *buffer, + utf8proc_ssize_t length, + utf8proc_option_t options); + +/** + * Given a pair of consecutive codepoints, return whether a grapheme break is + * permitted between them (as defined by the extended grapheme clusters in UAX#29). + * + * @param state Beginning with Version 29 (Unicode 9.0.0), this algorithm requires + * state to break graphemes. This state can be passed in as a pointer + * in the `state` argument and should initially be set to 0. If the + * state is not passed in (i.e. a null pointer is passed), UAX#29 rules + * GB10/12/13 which require this state will not be applied, essentially + * matching the rules in Unicode 8.0.0. + * + * @warning If the state parameter is used, `utf8proc_grapheme_break_stateful` must + * be called IN ORDER on ALL potential breaks in a string. + */ +UTF8PROC_DLLEXPORT utf8proc_bool utf8proc_grapheme_break_stateful( + utf8proc_int32_t codepoint1, + utf8proc_int32_t codepoint2, + utf8proc_int32_t *state); + +/** + * Same as @ref utf8proc_grapheme_break_stateful, except without support for the + * Unicode 9 additions to the algorithm. Supported for legacy reasons. + */ +UTF8PROC_DLLEXPORT utf8proc_bool utf8proc_grapheme_break(utf8proc_int32_t codepoint1, utf8proc_int32_t codepoint2); + + +/** + * Given a codepoint `c`, return the codepoint of the corresponding + * lower-case character, if any; otherwise (if there is no lower-case + * variant, or if `c` is not a valid codepoint) return `c`. + */ +UTF8PROC_DLLEXPORT utf8proc_int32_t utf8proc_tolower(utf8proc_int32_t c); + +/** + * Given a codepoint `c`, return the codepoint of the corresponding + * upper-case character, if any; otherwise (if there is no upper-case + * variant, or if `c` is not a valid codepoint) return `c`. + */ +UTF8PROC_DLLEXPORT utf8proc_int32_t utf8proc_toupper(utf8proc_int32_t c); + +/** + * Given a codepoint `c`, return the codepoint of the corresponding + * title-case character, if any; otherwise (if there is no title-case + * variant, or if `c` is not a valid codepoint) return `c`. + */ +UTF8PROC_DLLEXPORT utf8proc_int32_t utf8proc_totitle(utf8proc_int32_t c); + +/** + * Given a codepoint, return a character width analogous to `wcwidth(codepoint)`, + * except that a width of 0 is returned for non-printable codepoints + * instead of -1 as in `wcwidth`. + * + * @note + * If you want to check for particular types of non-printable characters, + * (analogous to `isprint` or `iscntrl`), use @ref utf8proc_category. */ +UTF8PROC_DLLEXPORT int utf8proc_charwidth(utf8proc_int32_t codepoint); + +/** + * Return the Unicode category for the codepoint (one of the + * @ref utf8proc_category_t constants.) + */ +UTF8PROC_DLLEXPORT utf8proc_category_t utf8proc_category(utf8proc_int32_t codepoint); + +/** + * Return the two-letter (nul-terminated) Unicode category string for + * the codepoint (e.g. `"Lu"` or `"Co"`). + */ +UTF8PROC_DLLEXPORT const char *utf8proc_category_string(utf8proc_int32_t codepoint); + +/** + * Maps the given UTF-8 string pointed to by `str` to a new UTF-8 + * string, allocated dynamically by `malloc` and returned via `dstptr`. + * + * If the @ref UTF8PROC_NULLTERM flag in the `options` field is set, + * the length is determined by a NULL terminator, otherwise the + * parameter `strlen` is evaluated to determine the string length, but + * in any case the result will be NULL terminated (though it might + * contain NULL characters with the string if `str` contained NULL + * characters). Other flags in the `options` field are passed to the + * functions defined above, and regarded as described. See also + * @ref utfproc_map_custom to supply a custom codepoint transformation. + * + * In case of success the length of the new string is returned, + * otherwise a negative error code is returned. + * + * @note The memory of the new UTF-8 string will have been allocated + * with `malloc`, and should therefore be deallocated with `free`. + */ +UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_map( + const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, utf8proc_uint8_t **dstptr, utf8proc_option_t options + ); + +/** + * Like @ref utf8proc_map, but also takes a `custom_func` mapping function + * that is called on each codepoint in `str` before any other transformations + * (along with a `custom_data` pointer that is passed through to `custom_func`). + * The `custom_func` argument is ignored if it is `NULL`. + */ +UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_map_custom( + const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, utf8proc_uint8_t **dstptr, utf8proc_option_t options, + utf8proc_custom_func custom_func, void *custom_data + ); + +/** @name Unicode normalization + * + * Returns a pointer to newly allocated memory of a NFD, NFC, NFKD, NFKC or + * NFKC_Casefold normalized version of the null-terminated string `str`. These + * are shortcuts to calling @ref utf8proc_map with @ref UTF8PROC_NULLTERM + * combined with @ref UTF8PROC_STABLE and flags indicating the normalization. + */ +/** @{ */ +/** NFD normalization (@ref UTF8PROC_DECOMPOSE). */ +UTF8PROC_DLLEXPORT utf8proc_uint8_t *utf8proc_NFD(const utf8proc_uint8_t *str); + +/** NFC normalization (@ref UTF8PROC_COMPOSE). */ +UTF8PROC_DLLEXPORT utf8proc_uint8_t *utf8proc_NFC(const utf8proc_uint8_t *str); + +/** NFKD normalization (@ref UTF8PROC_DECOMPOSE and @ref UTF8PROC_COMPAT). */ +UTF8PROC_DLLEXPORT utf8proc_uint8_t *utf8proc_NFKD(const utf8proc_uint8_t *str); + +/** NFKC normalization (@ref UTF8PROC_COMPOSE and @ref UTF8PROC_COMPAT). */ +UTF8PROC_DLLEXPORT utf8proc_uint8_t *utf8proc_NFKC(const utf8proc_uint8_t *str); + +/** + * NFKC_Casefold normalization (@ref UTF8PROC_COMPOSE and @ref UTF8PROC_COMPAT + * and @ref UTF8PROC_CASEFOLD and @ref UTF8PROC_IGNORE). + **/ +UTF8PROC_DLLEXPORT utf8proc_uint8_t *utf8proc_NFKC_Casefold(const utf8proc_uint8_t *str); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/uncrustify.cfg b/uncrustify.cfg new file mode 100644 index 0000000..b098398 --- /dev/null +++ b/uncrustify.cfg @@ -0,0 +1,3190 @@ +# Uncrustify-0.72.0-102-b22264bf + +# +# General options +# + +# The type of line endings. +# +# Default: auto +newlines = auto # lf/crlf/cr/auto + +# The original size of tabs in the input. +# +# Default: 8 +input_tab_size = 2 # unsigned number + +# The size of tabs in the output (only used if align_with_tabs=true). +# +# Default: 8 +output_tab_size = 2 # unsigned number + +# The ASCII value of the string escape char, usually 92 (\) or (Pawn) 94 (^). +# +# Default: 92 +string_escape_char = 92 # unsigned number + +# Alternate string escape char (usually only used for Pawn). +# Only works right before the quote char. +string_escape_char2 = 0 # unsigned number + +# Replace tab characters found in string literals with the escape sequence \t +# instead. +string_replace_tab_chars = false # true/false + +# Allow interpreting '>=' and '>>=' as part of a template in code like +# 'void f(list>=val);'. If true, 'assert(x<0 && y>=3)' will be broken. +# Improvements to template detection may make this option obsolete. +tok_split_gte = false # true/false + +# Disable formatting of NL_CONT ('\\n') ended lines (e.g. multiline macros) +disable_processing_nl_cont = false # true/false + +# Specify the marker used in comments to disable processing of part of the +# file. +# +# Default: *INDENT-OFF* +disable_processing_cmt = " *INDENT-OFF*" # string + +# Specify the marker used in comments to (re)enable processing in a file. +# +# Default: *INDENT-ON* +enable_processing_cmt = " *INDENT-ON*" # string + +# Enable parsing of digraphs. +enable_digraphs = false # true/false + +# Option to allow both disable_processing_cmt and enable_processing_cmt strings, +# if specified, to be interpreted as ECMAScript regular expressions. If true, +# a regex search will be performed within comments according to the specified +# patterns in order to disable/enable processing +processing_cmt_as_regex = false # true/false + +# Add or remove the UTF-8 BOM (recommend 'remove'). +utf8_bom = remove # ignore/add/remove/force + +# If the file contains bytes with values between 128 and 255, but is not +# UTF-8, then output as UTF-8. +utf8_byte = false # true/false + +# Force the output encoding to UTF-8. +utf8_force = false # true/false + +# Add or remove space between 'do' and '{'. +sp_do_brace_open = add # ignore/add/remove/force + +# Add or remove space between '}' and 'while'. +sp_brace_close_while = add # ignore/add/remove/force + +# Add or remove space between 'while' and '('. +sp_while_paren_open = add # ignore/add/remove/force + +# +# Spacing options +# + +# Add or remove space around non-assignment symbolic operators ('+', '/', '%', +# '<<', and so forth). +sp_arith = force # ignore/add/remove/force + +# Add or remove space around arithmetic operators '+' and '-'. +# +# Overrides sp_arith. +sp_arith_additive = ignore # ignore/add/remove/force + +# Add or remove space around assignment operator '=', '+=', etc. +sp_assign = force # ignore/add/remove/force + +# Add or remove space around '=' in C++11 lambda capture specifications. +# +# Overrides sp_assign. +sp_cpp_lambda_assign = ignore # ignore/add/remove/force + +# Add or remove space after the capture specification of a C++11 lambda when +# an argument list is present, as in '[] (int x){ ... }'. +sp_cpp_lambda_square_paren = ignore # ignore/add/remove/force + +# Add or remove space after the capture specification of a C++11 lambda with +# no argument list is present, as in '[] { ... }'. +sp_cpp_lambda_square_brace = ignore # ignore/add/remove/force + +# Add or remove space after the argument list of a C++11 lambda, as in +# '[](int x) { ... }'. +sp_cpp_lambda_paren_brace = ignore # ignore/add/remove/force + +# Add or remove space between a lambda body and its call operator of an +# immediately invoked lambda, as in '[]( ... ){ ... } ( ... )'. +sp_cpp_lambda_fparen = ignore # ignore/add/remove/force + +# Add or remove space around assignment operator '=' in a prototype. +# +# If set to ignore, use sp_assign. +sp_assign_default = ignore # ignore/add/remove/force + +# Add or remove space before assignment operator '=', '+=', etc. +# +# Overrides sp_assign. +sp_before_assign = ignore # ignore/add/remove/force + +# Add or remove space after assignment operator '=', '+=', etc. +# +# Overrides sp_assign. +sp_after_assign = ignore # ignore/add/remove/force + +# Add or remove space in 'NS_ENUM ('. +sp_enum_paren = ignore # ignore/add/remove/force + +# Add or remove space around assignment '=' in enum. +sp_enum_assign = ignore # ignore/add/remove/force + +# Add or remove space before assignment '=' in enum. +# +# Overrides sp_enum_assign. +sp_enum_before_assign = ignore # ignore/add/remove/force + +# Add or remove space after assignment '=' in enum. +# +# Overrides sp_enum_assign. +sp_enum_after_assign = ignore # ignore/add/remove/force + +# Add or remove space around assignment ':' in enum. +sp_enum_colon = ignore # ignore/add/remove/force + +# Add or remove space around preprocessor '##' concatenation operator. +# +# Default: add +sp_pp_concat = add # ignore/add/remove/force + +# Add or remove space after preprocessor '#' stringify operator. +# Also affects the '#@' charizing operator. +sp_pp_stringify = remove # ignore/add/remove/force + +# Add or remove space before preprocessor '#' stringify operator +# as in '#define x(y) L#y'. +sp_before_pp_stringify = remove # ignore/add/remove/force + +# Add or remove space around boolean operators '&&' and '||'. +sp_bool = force # ignore/add/remove/force + +# Add or remove space around compare operator '<', '>', '==', etc. +sp_compare = force # ignore/add/remove/force + +# Add or remove space inside '(' and ')'. +sp_inside_paren = remove # ignore/add/remove/force + +# Add or remove space between nested parentheses, i.e. '((' vs. ') )'. +sp_paren_paren = ignore # ignore/add/remove/force + +# Add or remove space between back-to-back parentheses, i.e. ')(' vs. ') ('. +sp_cparen_oparen = add # ignore/add/remove/force + +# Whether to balance spaces inside nested parentheses. +sp_balance_nested_parens = false # true/false + +# Add or remove space between ')' and '{'. +sp_paren_brace = force # ignore/add/remove/force + +# Add or remove space between nested braces, i.e. '{{' vs '{ {'. +sp_brace_brace = remove # ignore/add/remove/force + +# Add or remove space before pointer star '*'. +sp_before_ptr_star = force # ignore/add/remove/force + +# Add or remove space before pointer star '*' that isn't followed by a +# variable name. If set to ignore, sp_before_ptr_star is used instead. +sp_before_unnamed_ptr_star = remove # ignore/add/remove/force + +# Add or remove space between pointer stars '*'. +sp_between_ptr_star = remove # ignore/add/remove/force + +# Add or remove space after pointer star '*', if followed by a word. +# +# Overrides sp_type_func. +sp_after_ptr_star = remove # ignore/add/remove/force + +# Add or remove space after pointer caret '^', if followed by a word. +sp_after_ptr_block_caret = ignore # ignore/add/remove/force + +# Add or remove space after pointer star '*', if followed by a qualifier. +sp_after_ptr_star_qualifier = force # ignore/add/remove/force + +# Add or remove space after a pointer star '*', if followed by a function +# prototype or function definition. +# +# Overrides sp_after_ptr_star and sp_type_func. +sp_after_ptr_star_func = remove # ignore/add/remove/force + +# Add or remove space after a pointer star '*', if followed by an open +# parenthesis, as in 'void* (*)(). +sp_ptr_star_paren = force # ignore/add/remove/force + +# Add or remove space before a pointer star '*', if followed by a function +# prototype or function definition. +sp_before_ptr_star_func = force # ignore/add/remove/force + +# Add or remove space before a reference sign '&'. +sp_before_byref = ignore # ignore/add/remove/force + +# Add or remove space before a reference sign '&' that isn't followed by a +# variable name. If set to ignore, sp_before_byref is used instead. +sp_before_unnamed_byref = ignore # ignore/add/remove/force + +# Add or remove space after reference sign '&', if followed by a word. +# +# Overrides sp_type_func. +sp_after_byref = ignore # ignore/add/remove/force + +# Add or remove space after a reference sign '&', if followed by a function +# prototype or function definition. +# +# Overrides sp_after_byref and sp_type_func. +sp_after_byref_func = ignore # ignore/add/remove/force + +# Add or remove space before a reference sign '&', if followed by a function +# prototype or function definition. +sp_before_byref_func = ignore # ignore/add/remove/force + +# Add or remove space between type and word. In cases where total removal of +# whitespace would be a syntax error, a value of 'remove' is treated the same +# as 'force'. +# +# This also affects some other instances of space following a type that are +# not covered by other options; for example, between the return type and +# parenthesis of a function type template argument, between the type and +# parenthesis of an array parameter, or between 'decltype(...)' and the +# following word. +# +# Default: force +sp_after_type = force # ignore/add/remove/force + +# Add or remove space between 'decltype(...)' and word. +# +# Overrides sp_after_type. +sp_after_decltype = ignore # ignore/add/remove/force + +# (D) Add or remove space before the parenthesis in the D constructs +# 'template Foo(' and 'class Foo('. +sp_before_template_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'template' and '<'. +# If set to ignore, sp_before_angle is used. +sp_template_angle = ignore # ignore/add/remove/force + +# Add or remove space before '<'. +sp_before_angle = ignore # ignore/add/remove/force + +# Add or remove space inside '<' and '>'. +sp_inside_angle = ignore # ignore/add/remove/force + +# Add or remove space inside '<>'. +sp_inside_angle_empty = ignore # ignore/add/remove/force + +# Add or remove space between '>' and ':'. +sp_angle_colon = ignore # ignore/add/remove/force + +# Add or remove space after '>'. +sp_after_angle = ignore # ignore/add/remove/force + +# Add or remove space between '>' and '(' as found in 'new List(foo);'. +sp_angle_paren = ignore # ignore/add/remove/force + +# Add or remove space between '>' and '()' as found in 'new List();'. +sp_angle_paren_empty = ignore # ignore/add/remove/force + +# Add or remove space between '>' and a word as in 'List m;' or +# 'template static ...'. +sp_angle_word = ignore # ignore/add/remove/force + +# Add or remove space between '>' and '>' in '>>' (template stuff). +# +# Default: add +sp_angle_shift = add # ignore/add/remove/force + +# (C++11) Permit removal of the space between '>>' in 'foo >'. Note +# that sp_angle_shift cannot remove the space without this option. +sp_permit_cpp11_shift = false # true/false + +# Add or remove space before '(' of control statements ('if', 'for', 'switch', +# 'while', etc.). +sp_before_sparen = force # ignore/add/remove/force + +# Add or remove space inside '(' and ')' of control statements. +sp_inside_sparen = remove # ignore/add/remove/force + +# Add or remove space after '(' of control statements. +# +# Overrides sp_inside_sparen. +sp_inside_sparen_open = ignore # ignore/add/remove/force + +# Add or remove space before ')' of control statements. +# +# Overrides sp_inside_sparen. +sp_inside_sparen_close = ignore # ignore/add/remove/force + +# Add or remove space after ')' of control statements. +sp_after_sparen = force # ignore/add/remove/force + +# Add or remove space between ')' and '{' of of control statements. +sp_sparen_brace = force # ignore/add/remove/force + +# (D) Add or remove space between 'invariant' and '('. +sp_invariant_paren = ignore # ignore/add/remove/force + +# (D) Add or remove space after the ')' in 'invariant (C) c'. +sp_after_invariant_paren = ignore # ignore/add/remove/force + +# Add or remove space before empty statement ';' on 'if', 'for' and 'while'. +sp_special_semi = add # ignore/add/remove/force + +# Add or remove space before ';'. +# +# Default: remove +sp_before_semi = remove # ignore/add/remove/force + +# Add or remove space before ';' in non-empty 'for' statements. +sp_before_semi_for = remove # ignore/add/remove/force + +# Add or remove space before a semicolon of an empty part of a for statement. +sp_before_semi_for_empty = add # ignore/add/remove/force + +# Add or remove space after ';', except when followed by a comment. +# +# Default: add +sp_after_semi = add # ignore/add/remove/force + +# Add or remove space after ';' in non-empty 'for' statements. +# +# Default: force +sp_after_semi_for = force # ignore/add/remove/force + +# Add or remove space after the final semicolon of an empty part of a for +# statement, as in 'for ( ; ; )'. +sp_after_semi_for_empty = add # ignore/add/remove/force + +# Add or remove space before '[' (except '[]'). +sp_before_square = ignore # ignore/add/remove/force + +# Add or remove space before '[' for a variable definition. +# +# Default: remove +sp_before_vardef_square = remove # ignore/add/remove/force + +# Add or remove space before '[' for asm block. +sp_before_square_asm_block = ignore # ignore/add/remove/force + +# Add or remove space before '[]'. +sp_before_squares = ignore # ignore/add/remove/force + +# Add or remove space before C++17 structured bindings. +sp_cpp_before_struct_binding = ignore # ignore/add/remove/force + +# Add or remove space inside a non-empty '[' and ']'. +sp_inside_square = ignore # ignore/add/remove/force + +# Add or remove space inside '[]'. +sp_inside_square_empty = remove # ignore/add/remove/force + +# (OC) Add or remove space inside a non-empty Objective-C boxed array '@[' and +# ']'. If set to ignore, sp_inside_square is used. +sp_inside_square_oc_array = ignore # ignore/add/remove/force + +# Add or remove space after ',', i.e. 'a,b' vs. 'a, b'. +sp_after_comma = force # ignore/add/remove/force + +# Add or remove space before ','. +# +# Default: remove +sp_before_comma = remove # ignore/add/remove/force + +# (C#) Add or remove space between ',' and ']' in multidimensional array type +# like 'int[,,]'. +sp_after_mdatype_commas = ignore # ignore/add/remove/force + +# (C#) Add or remove space between '[' and ',' in multidimensional array type +# like 'int[,,]'. +sp_before_mdatype_commas = ignore # ignore/add/remove/force + +# (C#) Add or remove space between ',' in multidimensional array type +# like 'int[,,]'. +sp_between_mdatype_commas = ignore # ignore/add/remove/force + +# Add or remove space between an open parenthesis and comma, +# i.e. '(,' vs. '( ,'. +# +# Default: force +sp_paren_comma = force # ignore/add/remove/force + +# Add or remove space before the variadic '...' when preceded by a +# non-punctuator. +sp_before_ellipsis = force # ignore/add/remove/force + +# Add or remove space between a type and '...'. +sp_type_ellipsis = ignore # ignore/add/remove/force + +# (D) Add or remove space between a type and '?'. +sp_type_question = ignore # ignore/add/remove/force + +# Add or remove space between ')' and '...'. +sp_paren_ellipsis = remove # ignore/add/remove/force + +# Add or remove space between ')' and a qualifier such as 'const'. +sp_paren_qualifier = ignore # ignore/add/remove/force + +# Add or remove space between ')' and 'noexcept'. +sp_paren_noexcept = ignore # ignore/add/remove/force + +# Add or remove space after class ':'. +sp_after_class_colon = ignore # ignore/add/remove/force + +# Add or remove space before class ':'. +sp_before_class_colon = ignore # ignore/add/remove/force + +# Add or remove space after class constructor ':'. +sp_after_constr_colon = ignore # ignore/add/remove/force + +# Add or remove space before class constructor ':'. +sp_before_constr_colon = ignore # ignore/add/remove/force + +# Add or remove space before case ':'. +# +# Default: remove +sp_before_case_colon = remove # ignore/add/remove/force + +# Add or remove space between 'operator' and operator sign. +sp_after_operator = ignore # ignore/add/remove/force + +# Add or remove space between the operator symbol and the open parenthesis, as +# in 'operator ++('. +sp_after_operator_sym = ignore # ignore/add/remove/force + +# Overrides sp_after_operator_sym when the operator has no arguments, as in +# 'operator *()'. +sp_after_operator_sym_empty = ignore # ignore/add/remove/force + +# Add or remove space after C/D cast, i.e. 'cast(int)a' vs. 'cast(int) a' or +# '(int)a' vs. '(int) a'. +sp_after_cast = force # ignore/add/remove/force + +# Add or remove spaces inside cast parentheses. +sp_inside_paren_cast = remove # ignore/add/remove/force + +# Add or remove space between the type and open parenthesis in a C++ cast, +# i.e. 'int(exp)' vs. 'int (exp)'. +sp_cpp_cast_paren = remove # ignore/add/remove/force + +# Add or remove space between 'sizeof' and '('. +sp_sizeof_paren = remove # ignore/add/remove/force + +# Add or remove space between 'sizeof' and '...'. +sp_sizeof_ellipsis = ignore # ignore/add/remove/force + +# Add or remove space between 'sizeof...' and '('. +sp_sizeof_ellipsis_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'decltype' and '('. +sp_decltype_paren = ignore # ignore/add/remove/force + +# (Pawn) Add or remove space after the tag keyword. +sp_after_tag = ignore # ignore/add/remove/force + +# Add or remove space inside enum '{' and '}'. +sp_inside_braces_enum = force # ignore/add/remove/force + +# Add or remove space inside struct/union '{' and '}'. +sp_inside_braces_struct = force # ignore/add/remove/force + +# (OC) Add or remove space inside Objective-C boxed dictionary '{' and '}' +sp_inside_braces_oc_dict = ignore # ignore/add/remove/force + +# Add or remove space after open brace in an unnamed temporary +# direct-list-initialization. +sp_after_type_brace_init_lst_open = ignore # ignore/add/remove/force + +# Add or remove space before close brace in an unnamed temporary +# direct-list-initialization. +sp_before_type_brace_init_lst_close = ignore # ignore/add/remove/force + +# Add or remove space inside an unnamed temporary direct-list-initialization. +sp_inside_type_brace_init_lst = ignore # ignore/add/remove/force + +# Add or remove space inside '{' and '}'. +sp_inside_braces = force # ignore/add/remove/force + +# Add or remove space inside '{}'. +sp_inside_braces_empty = remove # ignore/add/remove/force + +# Add or remove space around trailing return operator '->'. +sp_trailing_return = ignore # ignore/add/remove/force + +# Add or remove space between return type and function name. A minimum of 1 +# is forced except for pointer return types. +sp_type_func = force # ignore/add/remove/force + +# Add or remove space between type and open brace of an unnamed temporary +# direct-list-initialization. +sp_type_brace_init_lst = ignore # ignore/add/remove/force + +# Add or remove space between function name and '(' on function declaration. +sp_func_proto_paren = remove # ignore/add/remove/force + +# Add or remove space between function name and '()' on function declaration +# without parameters. +sp_func_proto_paren_empty = ignore # ignore/add/remove/force + +# Add or remove space between function name and '(' with a typedef specifier. +sp_func_type_paren = remove # ignore/add/remove/force + +# Add or remove space between alias name and '(' of a non-pointer function type typedef. +sp_func_def_paren = remove # ignore/add/remove/force + +# Add or remove space between function name and '()' on function definition +# without parameters. +sp_func_def_paren_empty = remove # ignore/add/remove/force + +# Add or remove space inside empty function '()'. +# Overrides sp_after_angle unless use_sp_after_angle_always is set to true. +sp_inside_fparens = remove # ignore/add/remove/force + +# Add or remove space inside function '(' and ')'. +sp_inside_fparen = remove # ignore/add/remove/force + +# Add or remove space inside the first parentheses in a function type, as in +# 'void (*x)(...)'. +sp_inside_tparen = remove # ignore/add/remove/force + +# Add or remove space between the ')' and '(' in a function type, as in +# 'void (*x)(...)'. +sp_after_tparen_close = remove # ignore/add/remove/force + +# Add or remove space between ']' and '(' when part of a function call. +sp_square_fparen = remove # ignore/add/remove/force + +# Add or remove space between ')' and '{' of function. +sp_fparen_brace = force # ignore/add/remove/force + +# Add or remove space between ')' and '{' of a function call in object +# initialization. +# +# Overrides sp_fparen_brace. +sp_fparen_brace_initializer = force # ignore/add/remove/force + +# (Java) Add or remove space between ')' and '{{' of double brace initializer. +sp_fparen_dbrace = ignore # ignore/add/remove/force + +# Add or remove space between function name and '(' on function calls. +sp_func_call_paren = remove # ignore/add/remove/force + +# Add or remove space between function name and '()' on function calls without +# parameters. If set to ignore (the default), sp_func_call_paren is used. +sp_func_call_paren_empty = remove # ignore/add/remove/force + +# Add or remove space between the user function name and '(' on function +# calls. You need to set a keyword to be a user function in the config file, +# like: +# set func_call_user tr _ i18n +sp_func_call_user_paren = ignore # ignore/add/remove/force + +# Add or remove space inside user function '(' and ')'. +sp_func_call_user_inside_fparen = ignore # ignore/add/remove/force + +# Add or remove space between nested parentheses with user functions, +# i.e. '((' vs. '( ('. +sp_func_call_user_paren_paren = ignore # ignore/add/remove/force + +# Add or remove space between a constructor/destructor and the open +# parenthesis. +sp_func_class_paren = remove # ignore/add/remove/force + +# Add or remove space between a constructor without parameters or destructor +# and '()'. +sp_func_class_paren_empty = ignore # ignore/add/remove/force + +# Add or remove space between 'return' and '('. +sp_return_paren = force # ignore/add/remove/force + +# Add or remove space between 'return' and '{'. +sp_return_brace = force # ignore/add/remove/force + +# Add or remove space between '__attribute__' and '('. +sp_attribute_paren = remove # ignore/add/remove/force + +# Add or remove space between 'defined' and '(' in '#if defined (FOO)'. +sp_defined_paren = remove # ignore/add/remove/force + +# Add or remove space between 'throw' and '(' in 'throw (something)'. +sp_throw_paren = force # ignore/add/remove/force + +# Add or remove space between 'throw' and anything other than '(' as in +# '@throw [...];'. +sp_after_throw = force # ignore/add/remove/force + +# Add or remove space between 'catch' and '(' in 'catch (something) { }'. +# If set to ignore, sp_before_sparen is used. +sp_catch_paren = remove # ignore/add/remove/force + +# (OC) Add or remove space between '@catch' and '(' +# in '@catch (something) { }'. If set to ignore, sp_catch_paren is used. +sp_oc_catch_paren = ignore # ignore/add/remove/force + +# (OC) Add or remove space before Objective-C protocol list +# as in '@protocol Protocol' or '@interface MyClass : NSObject'. +sp_before_oc_proto_list = ignore # ignore/add/remove/force + +# (OC) Add or remove space between class name and '(' +# in '@interface className(categoryName):BaseClass' +sp_oc_classname_paren = ignore # ignore/add/remove/force + +# (D) Add or remove space between 'version' and '(' +# in 'version (something) { }'. If set to ignore, sp_before_sparen is used. +sp_version_paren = ignore # ignore/add/remove/force + +# (D) Add or remove space between 'scope' and '(' +# in 'scope (something) { }'. If set to ignore, sp_before_sparen is used. +sp_scope_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'super' and '(' in 'super (something)'. +# +# Default: remove +sp_super_paren = remove # ignore/add/remove/force + +# Add or remove space between 'this' and '(' in 'this (something)'. +# +# Default: remove +sp_this_paren = remove # ignore/add/remove/force + +# Add or remove space between a macro name and its definition. +sp_macro = force # ignore/add/remove/force + +# Add or remove space between a macro function ')' and its definition. +sp_macro_func = ignore # ignore/add/remove/force + +# Add or remove space between 'else' and '{' if on the same line. +sp_else_brace = force # ignore/add/remove/force + +# Add or remove space between '}' and 'else' if on the same line. +sp_brace_else = force # ignore/add/remove/force + +# Add or remove space between '}' and the name of a typedef on the same line. +sp_brace_typedef = force # ignore/add/remove/force + +# Add or remove space before the '{' of a 'catch' statement, if the '{' and +# 'catch' are on the same line, as in 'catch (decl) {'. +sp_catch_brace = ignore # ignore/add/remove/force + +# (OC) Add or remove space before the '{' of a '@catch' statement, if the '{' +# and '@catch' are on the same line, as in '@catch (decl) {'. +# If set to ignore, sp_catch_brace is used. +sp_oc_catch_brace = ignore # ignore/add/remove/force + +# Add or remove space between '}' and 'catch' if on the same line. +sp_brace_catch = ignore # ignore/add/remove/force + +# (OC) Add or remove space between '}' and '@catch' if on the same line. +# If set to ignore, sp_brace_catch is used. +sp_oc_brace_catch = ignore # ignore/add/remove/force + +# Add or remove space between 'finally' and '{' if on the same line. +sp_finally_brace = ignore # ignore/add/remove/force + +# Add or remove space between '}' and 'finally' if on the same line. +sp_brace_finally = ignore # ignore/add/remove/force + +# Add or remove space between 'try' and '{' if on the same line. +sp_try_brace = ignore # ignore/add/remove/force + +# Add or remove space between get/set and '{' if on the same line. +sp_getset_brace = ignore # ignore/add/remove/force + +# Add or remove space between a variable and '{' for C++ uniform +# initialization. +sp_word_brace_init_lst = ignore # ignore/add/remove/force + +# Add or remove space between a variable and '{' for a namespace. +# +# Default: add +sp_word_brace_ns = add # ignore/add/remove/force + +# Add or remove space before the '::' operator. +sp_before_dc = ignore # ignore/add/remove/force + +# Add or remove space after the '::' operator. +sp_after_dc = ignore # ignore/add/remove/force + +# (D) Add or remove around the D named array initializer ':' operator. +sp_d_array_colon = ignore # ignore/add/remove/force + +# Add or remove space after the '!' (not) unary operator. +# +# Default: remove +sp_not = remove # ignore/add/remove/force + +# Add or remove space after the '~' (invert) unary operator. +# +# Default: remove +sp_inv = remove # ignore/add/remove/force + +# Add or remove space after the '&' (address-of) unary operator. This does not +# affect the spacing after a '&' that is part of a type. +# +# Default: remove +sp_addr = remove # ignore/add/remove/force + +# Add or remove space around the '.' or '->' operators. +# +# Default: remove +sp_member = remove # ignore/add/remove/force + +# Add or remove space after the '*' (dereference) unary operator. This does +# not affect the spacing after a '*' that is part of a type. +# +# Default: remove +sp_deref = remove # ignore/add/remove/force + +# Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'. +# +# Default: remove +sp_sign = remove # ignore/add/remove/force + +# Add or remove space between '++' and '--' the word to which it is being +# applied, as in '(--x)' or 'y++;'. +# +# Default: remove +sp_incdec = remove # ignore/add/remove/force + +# Add or remove space before a backslash-newline at the end of a line. +# +# Default: add +sp_before_nl_cont = add # ignore/add/remove/force + +# (OC) Add or remove space after the scope '+' or '-', as in '-(void) foo;' +# or '+(int) bar;'. +sp_after_oc_scope = ignore # ignore/add/remove/force + +# (OC) Add or remove space after the colon in message specs, +# i.e. '-(int) f:(int) x;' vs. '-(int) f: (int) x;'. +sp_after_oc_colon = ignore # ignore/add/remove/force + +# (OC) Add or remove space before the colon in message specs, +# i.e. '-(int) f: (int) x;' vs. '-(int) f : (int) x;'. +sp_before_oc_colon = ignore # ignore/add/remove/force + +# (OC) Add or remove space after the colon in immutable dictionary expression +# 'NSDictionary *test = @{@"foo" :@"bar"};'. +sp_after_oc_dict_colon = ignore # ignore/add/remove/force + +# (OC) Add or remove space before the colon in immutable dictionary expression +# 'NSDictionary *test = @{@"foo" :@"bar"};'. +sp_before_oc_dict_colon = ignore # ignore/add/remove/force + +# (OC) Add or remove space after the colon in message specs, +# i.e. '[object setValue:1];' vs. '[object setValue: 1];'. +sp_after_send_oc_colon = ignore # ignore/add/remove/force + +# (OC) Add or remove space before the colon in message specs, +# i.e. '[object setValue:1];' vs. '[object setValue :1];'. +sp_before_send_oc_colon = ignore # ignore/add/remove/force + +# (OC) Add or remove space after the (type) in message specs, +# i.e. '-(int)f: (int) x;' vs. '-(int)f: (int)x;'. +sp_after_oc_type = ignore # ignore/add/remove/force + +# (OC) Add or remove space after the first (type) in message specs, +# i.e. '-(int) f:(int)x;' vs. '-(int)f:(int)x;'. +sp_after_oc_return_type = ignore # ignore/add/remove/force + +# (OC) Add or remove space between '@selector' and '(', +# i.e. '@selector(msgName)' vs. '@selector (msgName)'. +# Also applies to '@protocol()' constructs. +sp_after_oc_at_sel = ignore # ignore/add/remove/force + +# (OC) Add or remove space between '@selector(x)' and the following word, +# i.e. '@selector(foo) a:' vs. '@selector(foo)a:'. +sp_after_oc_at_sel_parens = ignore # ignore/add/remove/force + +# (OC) Add or remove space inside '@selector' parentheses, +# i.e. '@selector(foo)' vs. '@selector( foo )'. +# Also applies to '@protocol()' constructs. +sp_inside_oc_at_sel_parens = ignore # ignore/add/remove/force + +# (OC) Add or remove space before a block pointer caret, +# i.e. '^int (int arg){...}' vs. ' ^int (int arg){...}'. +sp_before_oc_block_caret = ignore # ignore/add/remove/force + +# (OC) Add or remove space after a block pointer caret, +# i.e. '^int (int arg){...}' vs. '^ int (int arg){...}'. +sp_after_oc_block_caret = ignore # ignore/add/remove/force + +# (OC) Add or remove space between the receiver and selector in a message, +# as in '[receiver selector ...]'. +sp_after_oc_msg_receiver = ignore # ignore/add/remove/force + +# (OC) Add or remove space after '@property'. +sp_after_oc_property = ignore # ignore/add/remove/force + +# (OC) Add or remove space between '@synchronized' and the open parenthesis, +# i.e. '@synchronized(foo)' vs. '@synchronized (foo)'. +sp_after_oc_synchronized = ignore # ignore/add/remove/force + +# Add or remove space around the ':' in 'b ? t : f'. +sp_cond_colon = force # ignore/add/remove/force + +# Add or remove space before the ':' in 'b ? t : f'. +# +# Overrides sp_cond_colon. +sp_cond_colon_before = ignore # ignore/add/remove/force + +# Add or remove space after the ':' in 'b ? t : f'. +# +# Overrides sp_cond_colon. +sp_cond_colon_after = ignore # ignore/add/remove/force + +# Add or remove space around the '?' in 'b ? t : f'. +sp_cond_question = force # ignore/add/remove/force + +# Add or remove space before the '?' in 'b ? t : f'. +# +# Overrides sp_cond_question. +sp_cond_question_before = ignore # ignore/add/remove/force + +# Add or remove space after the '?' in 'b ? t : f'. +# +# Overrides sp_cond_question. +sp_cond_question_after = ignore # ignore/add/remove/force + +# In the abbreviated ternary form '(a ?: b)', add or remove space between '?' +# and ':'. +# +# Overrides all other sp_cond_* options. +sp_cond_ternary_short = remove # ignore/add/remove/force + +# Fix the spacing between 'case' and the label. Only 'ignore' and 'force' make +# sense here. +sp_case_label = force # ignore/add/remove/force + +# (D) Add or remove space around the D '..' operator. +sp_range = ignore # ignore/add/remove/force + +# Add or remove space after ':' in a Java/C++11 range-based 'for', +# as in 'for (Type var : expr)'. +sp_after_for_colon = ignore # ignore/add/remove/force + +# Add or remove space before ':' in a Java/C++11 range-based 'for', +# as in 'for (Type var : expr)'. +sp_before_for_colon = ignore # ignore/add/remove/force + +# (D) Add or remove space between 'extern' and '(' as in 'extern (C)'. +sp_extern_paren = ignore # ignore/add/remove/force + +# Add or remove space after the opening of a C++ comment, as in '// A'. +sp_cmt_cpp_start = ignore # ignore/add/remove/force + +# Add or remove space in a C++ region marker comment, as in '// BEGIN'. +# A region marker is defined as a comment which is not preceded by other text +# (i.e. the comment is the first non-whitespace on the line), and which starts +# with either 'BEGIN' or 'END'. +# +# Overrides sp_cmt_cpp_start. +sp_cmt_cpp_region = ignore # ignore/add/remove/force + +# If true, space added with sp_cmt_cpp_start will be added after Doxygen +# sequences like '///', '///<', '//!' and '//!<'. +sp_cmt_cpp_doxygen = false # true/false + +# If true, space added with sp_cmt_cpp_start will be added after Qt translator +# or meta-data comments like '//:', '//=', and '//~'. +sp_cmt_cpp_qttr = false # true/false + +# Add or remove space between #else or #endif and a trailing comment. +sp_endif_cmt = ignore # ignore/add/remove/force + +# Add or remove space after 'new', 'delete' and 'delete[]'. +sp_after_new = force # ignore/add/remove/force + +# Add or remove space between 'new' and '(' in 'new()'. +sp_between_new_paren = ignore # ignore/add/remove/force + +# Add or remove space between ')' and type in 'new(foo) BAR'. +sp_after_newop_paren = ignore # ignore/add/remove/force + +# Add or remove space inside parenthesis of the new operator +# as in 'new(foo) BAR'. +sp_inside_newop_paren = ignore # ignore/add/remove/force + +# Add or remove space after the open parenthesis of the new operator, +# as in 'new(foo) BAR'. +# +# Overrides sp_inside_newop_paren. +sp_inside_newop_paren_open = ignore # ignore/add/remove/force + +# Add or remove space before the close parenthesis of the new operator, +# as in 'new(foo) BAR'. +# +# Overrides sp_inside_newop_paren. +sp_inside_newop_paren_close = ignore # ignore/add/remove/force + +# Add or remove space before a trailing or embedded comment. +sp_before_tr_emb_cmt = ignore # ignore/add/remove/force + +# Number of spaces before a trailing or embedded comment. +sp_num_before_tr_emb_cmt = 0 # unsigned number + +# (Java) Add or remove space between an annotation and the open parenthesis. +sp_annotation_paren = ignore # ignore/add/remove/force + +# If true, vbrace tokens are dropped to the previous token and skipped. +sp_skip_vbrace_tokens = false # true/false + +# Add or remove space after 'noexcept'. +sp_after_noexcept = ignore # ignore/add/remove/force + +# Add or remove space after '_'. +sp_vala_after_translation = ignore # ignore/add/remove/force + +# If true, a is inserted after #define. +force_tab_after_define = false # true/false + +# +# Indenting options +# + +# The number of columns to indent per level. Usually 2, 3, 4, or 8. +# +# Default: 8 +indent_columns = 2 # unsigned number + +# The continuation indent. If non-zero, this overrides the indent of '(', '[' +# and '=' continuation indents. Negative values are OK; negative value is +# absolute and not increased for each '(' or '[' level. +# +# For FreeBSD, this is set to 4. +indent_continue = 0 # number + +# The continuation indent, only for class header line(s). If non-zero, this +# overrides the indent of 'class' continuation indents. +indent_continue_class_head = 0 # unsigned number + +# Whether to indent empty lines (i.e. lines which contain only spaces before +# the newline character). +indent_single_newlines = false # true/false + +# The continuation indent for func_*_param if they are true. If non-zero, this +# overrides the indent. +indent_param = 0 # unsigned number + +# How to use tabs when indenting code. +# +# 0: Spaces only +# 1: Indent with tabs to brace level, align with spaces (default) +# 2: Indent and align with tabs, using spaces when not on a tabstop +# +# Default: 1 +indent_with_tabs = 0 # unsigned number + +# Whether to indent comments that are not at a brace level with tabs on a +# tabstop. Requires indent_with_tabs=2. If false, will use spaces. +indent_cmt_with_tabs = false # true/false + +# Whether to indent strings broken by '\' so that they line up. +indent_align_string = false # true/false + +# The number of spaces to indent multi-line XML strings. +# Requires indent_align_string=true. +indent_xml_string = 0 # unsigned number + +# Spaces to indent '{' from level. +indent_brace = 0 # unsigned number + +# Whether braces are indented to the body level. +indent_braces = false # true/false + +# Whether to disable indenting function braces if indent_braces=true. +indent_braces_no_func = false # true/false + +# Whether to disable indenting class braces if indent_braces=true. +indent_braces_no_class = false # true/false + +# Whether to disable indenting struct braces if indent_braces=true. +indent_braces_no_struct = false # true/false + +# Whether to indent based on the size of the brace parent, +# i.e. 'if' => 3 spaces, 'for' => 4 spaces, etc. +indent_brace_parent = false # true/false + +# Whether to indent based on the open parenthesis instead of the open brace +# in '({\n'. +indent_paren_open_brace = false # true/false + +# (C#) Whether to indent the brace of a C# delegate by another level. +indent_cs_delegate_brace = false # true/false + +# (C#) Whether to indent a C# delegate (to handle delegates with no brace) by +# another level. +indent_cs_delegate_body = false # true/false + +# Whether to indent the body of a 'namespace'. +indent_namespace = false # true/false + +# Whether to indent only the first namespace, and not any nested namespaces. +# Requires indent_namespace=true. +indent_namespace_single_indent = false # true/false + +# The number of spaces to indent a namespace block. +# If set to zero, use the value indent_columns +indent_namespace_level = 0 # unsigned number + +# If the body of the namespace is longer than this number, it won't be +# indented. Requires indent_namespace=true. 0 means no limit. +indent_namespace_limit = 0 # unsigned number + +# Whether the 'extern "C"' body is indented. +indent_extern = false # true/false + +# Whether the 'class' body is indented. +indent_class = false # true/false + +# Additional indent before the leading base class colon. +# Negative values decrease indent down to the first column. +# Requires a newline break before colon (see pos_class_colon +# and nl_class_colon) +indent_before_class_colon = 0 # number + +# Whether to indent the stuff after a leading base class colon. +indent_class_colon = false # true/false + +# Whether to indent based on a class colon instead of the stuff after the +# colon. Requires indent_class_colon=true. +indent_class_on_colon = false # true/false + +# Whether to indent the stuff after a leading class initializer colon. +indent_constr_colon = false # true/false + +# Virtual indent from the ':' for member initializers. +# +# Default: 2 +indent_ctor_init_leading = 2 # unsigned number + +# Additional indent for constructor initializer list. +# Negative values decrease indent down to the first column. +indent_ctor_init = 0 # number + +# Whether to indent 'if' following 'else' as a new block under the 'else'. +# If false, 'else\nif' is treated as 'else if' for indenting purposes. +indent_else_if = false # true/false + +# Amount to indent variable declarations after a open brace. +# +# <0: Relative +# >=0: Absolute +indent_var_def_blk = 0 # number + +# Whether to indent continued variable declarations instead of aligning. +indent_var_def_cont = false # true/false + +# Whether to indent continued shift expressions ('<<' and '>>') instead of +# aligning. Set align_left_shift=false when enabling this. +indent_shift = false # true/false + +# Whether to force indentation of function definitions to start in column 1. +indent_func_def_force_col1 = false # true/false + +# Whether to indent continued function call parameters one indent level, +# rather than aligning parameters under the open parenthesis. +indent_func_call_param = false # true/false + +# Whether to indent continued function definition parameters one indent level, +# rather than aligning parameters under the open parenthesis. +indent_func_def_param = false # true/false + +# for function definitions, only if indent_func_def_param is false +# Allows to align params when appropriate and indent them when not +# behave as if it was true if paren position is more than this value +# if paren position is more than the option value +indent_func_def_param_paren_pos_threshold = 0 # unsigned number + +# Whether to indent continued function call prototype one indent level, +# rather than aligning parameters under the open parenthesis. +indent_func_proto_param = false # true/false + +# Whether to indent continued function call declaration one indent level, +# rather than aligning parameters under the open parenthesis. +indent_func_class_param = false # true/false + +# Whether to indent continued class variable constructors one indent level, +# rather than aligning parameters under the open parenthesis. +indent_func_ctor_var_param = false # true/false + +# Whether to indent continued template parameter list one indent level, +# rather than aligning parameters under the open parenthesis. +indent_template_param = false # true/false + +# Double the indent for indent_func_xxx_param options. +# Use both values of the options indent_columns and indent_param. +indent_func_param_double = false # true/false + +# Indentation column for standalone 'const' qualifier on a function +# prototype. +indent_func_const = 0 # unsigned number + +# Indentation column for standalone 'throw' qualifier on a function +# prototype. +indent_func_throw = 0 # unsigned number + +# How to indent within a macro followed by a brace on the same line +# This allows reducing the indent in macros that have (for example) +# `do { ... } while (0)` blocks bracketing them. +# +# true: add an indent for the brace on the same line as the macro +# false: do not add an indent for the brace on the same line as the macro +# +# Default: true +indent_macro_brace = true # true/false + +# The number of spaces to indent a continued '->' or '.'. +# Usually set to 0, 1, or indent_columns. +indent_member = indent_columns # unsigned number + +# Whether lines broken at '.' or '->' should be indented by a single indent. +# The indent_member option will not be effective if this is set to true. +indent_member_single = false # true/false + +# Spaces to indent single line ('//') comments on lines before code. +indent_sing_line_comments = 0 # unsigned number + +# When opening a paren for a control statement (if, for, while, etc), increase +# the indent level by this value. Negative values decrease the indent level. +indent_sparen_extra = 0 # number + +# Whether to indent trailing single line ('//') comments relative to the code +# instead of trying to keep the same absolute column. +indent_relative_single_line_comments = false # true/false + +# Spaces to indent 'case' from 'switch'. Usually 0 or indent_columns. +indent_switch_case = 2 # unsigned number + +# indent 'break' with 'case' from 'switch'. +indent_switch_break_with_case = false # true/false + +# Whether to indent preprocessor statements inside of switch statements. +# +# Default: true +indent_switch_pp = true # true/false + +# Spaces to shift the 'case' line, without affecting any other lines. +# Usually 0. +indent_case_shift = 0 # unsigned number + +# Spaces to indent '{' from 'case'. By default, the brace will appear under +# the 'c' in case. Usually set to 0 or indent_columns. Negative values are OK. +indent_case_brace = 0 # number + +# Whether to indent comments found in first column. +indent_col1_comment = false # true/false + +# Whether to indent multi string literal in first column. +indent_col1_multi_string_literal = false # true/false + +# How to indent goto labels. +# +# >0: Absolute column where 1 is the leftmost column +# <=0: Subtract from brace indent +# +# Default: 1 +indent_label = 1 # number + +# How to indent access specifiers that are followed by a +# colon. +# +# >0: Absolute column where 1 is the leftmost column +# <=0: Subtract from brace indent +# +# Default: 1 +indent_access_spec = 1 # number + +# Whether to indent the code after an access specifier by one level. +# If true, this option forces 'indent_access_spec=0'. +indent_access_spec_body = false # true/false + +# If an open parenthesis is followed by a newline, whether to indent the next +# line so that it lines up after the open parenthesis (not recommended). +indent_paren_nl = false # true/false + +# How to indent a close parenthesis after a newline. +# +# 0: Indent to body level (default) +# 1: Align under the open parenthesis +# 2: Indent to the brace level +indent_paren_close = 0 # unsigned number + +# Whether to indent the open parenthesis of a function definition, +# if the parenthesis is on its own line. +indent_paren_after_func_def = false # true/false + +# Whether to indent the open parenthesis of a function declaration, +# if the parenthesis is on its own line. +indent_paren_after_func_decl = false # true/false + +# Whether to indent the open parenthesis of a function call, +# if the parenthesis is on its own line. +indent_paren_after_func_call = false # true/false + +# Whether to indent a comma when inside a parenthesis. +# If true, aligns under the open parenthesis. +indent_comma_paren = false # true/false + +# Whether to indent a Boolean operator when inside a parenthesis. +# If true, aligns under the open parenthesis. +indent_bool_paren = true # true/false + +# Whether to indent a semicolon when inside a for parenthesis. +# If true, aligns under the open for parenthesis. +indent_semicolon_for_paren = false # true/false + +# Whether to align the first expression to following ones +# if indent_bool_paren=true. +indent_first_bool_expr = true # true/false + +# Whether to align the first expression to following ones +# if indent_semicolon_for_paren=true. +indent_first_for_expr = false # true/false + +# If an open square is followed by a newline, whether to indent the next line +# so that it lines up after the open square (not recommended). +indent_square_nl = false # true/false + +# (ESQL/C) Whether to preserve the relative indent of 'EXEC SQL' bodies. +indent_preserve_sql = false # true/false + +# Whether to align continued statements at the '='. If false or if the '=' is +# followed by a newline, the next line is indent one tab. +# +# Default: true +indent_align_assign = true # true/false + +# If true, the indentation of the chunks after a '=' sequence will be set at +# LHS token indentation column before '='. +indent_off_after_assign = false # true/false + +# Whether to align continued statements at the '('. If false or the '(' is +# followed by a newline, the next line indent is one tab. +# +# Default: true +indent_align_paren = true # true/false + +# (OC) Whether to indent Objective-C code inside message selectors. +indent_oc_inside_msg_sel = false # true/false + +# (OC) Whether to indent Objective-C blocks at brace level instead of usual +# rules. +indent_oc_block = false # true/false + +# (OC) Indent for Objective-C blocks in a message relative to the parameter +# name. +# +# =0: Use indent_oc_block rules +# >0: Use specified number of spaces to indent +indent_oc_block_msg = 0 # unsigned number + +# (OC) Minimum indent for subsequent parameters +indent_oc_msg_colon = 0 # unsigned number + +# (OC) Whether to prioritize aligning with initial colon (and stripping spaces +# from lines, if necessary). +# +# Default: true +indent_oc_msg_prioritize_first_colon = true # true/false + +# (OC) Whether to indent blocks the way that Xcode does by default +# (from the keyword if the parameter is on its own line; otherwise, from the +# previous indentation level). Requires indent_oc_block_msg=true. +indent_oc_block_msg_xcode_style = false # true/false + +# (OC) Whether to indent blocks from where the brace is, relative to a +# message keyword. Requires indent_oc_block_msg=true. +indent_oc_block_msg_from_keyword = false # true/false + +# (OC) Whether to indent blocks from where the brace is, relative to a message +# colon. Requires indent_oc_block_msg=true. +indent_oc_block_msg_from_colon = false # true/false + +# (OC) Whether to indent blocks from where the block caret is. +# Requires indent_oc_block_msg=true. +indent_oc_block_msg_from_caret = false # true/false + +# (OC) Whether to indent blocks from where the brace caret is. +# Requires indent_oc_block_msg=true. +indent_oc_block_msg_from_brace = false # true/false + +# When indenting after virtual brace open and newline add further spaces to +# reach this minimum indent. +indent_min_vbrace_open = 0 # unsigned number + +# Whether to add further spaces after regular indent to reach next tabstop +# when indenting after virtual brace open and newline. +indent_vbrace_open_on_tabstop = false # true/false + +# How to indent after a brace followed by another token (not a newline). +# true: indent all contained lines to match the token +# false: indent all contained lines to match the brace +# +# Default: true +indent_token_after_brace = true # true/false + +# Whether to indent the body of a C++11 lambda. +indent_cpp_lambda_body = false # true/false + +# How to indent compound literals that are being returned. +# true: add both the indent from return & the compound literal open brace (ie: +# 2 indent levels) +# false: only indent 1 level, don't add the indent for the open brace, only add +# the indent for the return. +# +# Default: true +indent_compound_literal_return = true # true/false + +# (C#) Whether to indent a 'using' block if no braces are used. +# +# Default: true +indent_using_block = true # true/false + +# How to indent the continuation of ternary operator. +# +# 0: Off (default) +# 1: When the `if_false` is a continuation, indent it under `if_false` +# 2: When the `:` is a continuation, indent it under `?` +indent_ternary_operator = 0 # unsigned number + +# Whether to indent the statments inside ternary operator. +indent_inside_ternary_operator = false # true/false + +# If true, the indentation of the chunks after a `return` sequence will be set at return indentation column. +indent_off_after_return = false # true/false + +# If true, the indentation of the chunks after a `return new` sequence will be set at return indentation column. +indent_off_after_return_new = false # true/false + +# If true, the tokens after return are indented with regular single indentation. By default (false) the indentation is after the return token. +indent_single_after_return = false # true/false + +# Whether to ignore indent and alignment for 'asm' blocks (i.e. assume they +# have their own indentation). +indent_ignore_asm_block = false # true/false + +# Don't indent the close parenthesis of a function definition, +# if the parenthesis is on its own line. +donot_indent_func_def_close_paren = false # true/false + +# +# Newline adding and removing options +# + +# Whether to collapse empty blocks between '{' and '}'. +# If true, overrides nl_inside_empty_func +nl_collapse_empty_body = false # true/false + +# Don't split one-line braced assignments, as in 'foo_t f = { 1, 2 };'. +nl_assign_leave_one_liners = true # true/false + +# Don't split one-line braced statements inside a 'class xx { }' body. +nl_class_leave_one_liners = false # true/false + +# Don't split one-line enums, as in 'enum foo { BAR = 15 };' +nl_enum_leave_one_liners = true # true/false + +# Don't split one-line get or set functions. +nl_getset_leave_one_liners = false # true/false + +# (C#) Don't split one-line property get or set functions. +nl_cs_property_leave_one_liners = false # true/false + +# Don't split one-line function definitions, as in 'int foo() { return 0; }'. +# might modify nl_func_type_name +nl_func_leave_one_liners = false # true/false + +# Don't split one-line C++11 lambdas, as in '[]() { return 0; }'. +nl_cpp_lambda_leave_one_liners = false # true/false + +# Don't split one-line if/else statements, as in 'if(...) b++;'. +nl_if_leave_one_liners = false # true/false + +# Don't split one-line while statements, as in 'while(...) b++;'. +nl_while_leave_one_liners = false # true/false + +# Don't split one-line for statements, as in 'for(...) b++;'. +nl_for_leave_one_liners = false # true/false + +# (OC) Don't split one-line Objective-C messages. +nl_oc_msg_leave_one_liner = false # true/false + +# (OC) Add or remove newline between method declaration and '{'. +nl_oc_mdef_brace = ignore # ignore/add/remove/force + +# (OC) Add or remove newline between Objective-C block signature and '{'. +nl_oc_block_brace = ignore # ignore/add/remove/force + +# (OC) Add or remove blank line before '@interface' statement. +nl_oc_before_interface = ignore # ignore/add/remove/force + +# (OC) Add or remove blank line before '@implementation' statement. +nl_oc_before_implementation = ignore # ignore/add/remove/force + +# (OC) Add or remove blank line before '@end' statement. +nl_oc_before_end = ignore # ignore/add/remove/force + +# (OC) Add or remove newline between '@interface' and '{'. +nl_oc_interface_brace = ignore # ignore/add/remove/force + +# (OC) Add or remove newline between '@implementation' and '{'. +nl_oc_implementation_brace = ignore # ignore/add/remove/force + +# Add or remove newlines at the start of the file. +nl_start_of_file = ignore # ignore/add/remove/force + +# The minimum number of newlines at the start of the file (only used if +# nl_start_of_file is 'add' or 'force'). +nl_start_of_file_min = 0 # unsigned number + +# Add or remove newline at the end of the file. +nl_end_of_file = add # ignore/add/remove/force + +# The minimum number of newlines at the end of the file (only used if +# nl_end_of_file is 'add' or 'force'). +nl_end_of_file_min = 0 # unsigned number + +# Add or remove newline between '=' and '{'. +nl_assign_brace = remove # ignore/add/remove/force + +# (D) Add or remove newline between '=' and '['. +nl_assign_square = ignore # ignore/add/remove/force + +# Add or remove newline between '[]' and '{'. +nl_tsquare_brace = ignore # ignore/add/remove/force + +# (D) Add or remove newline after '= ['. Will also affect the newline before +# the ']'. +nl_after_square_assign = ignore # ignore/add/remove/force + +# Add or remove newline between a function call's ')' and '{', as in +# 'list_for_each(item, &list) { }'. +nl_fcall_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'enum' and '{'. +nl_enum_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'enum' and 'class'. +nl_enum_class = ignore # ignore/add/remove/force + +# Add or remove newline between 'enum class' and the identifier. +nl_enum_class_identifier = ignore # ignore/add/remove/force + +# Add or remove newline between 'enum class' type and ':'. +nl_enum_identifier_colon = ignore # ignore/add/remove/force + +# Add or remove newline between 'enum class identifier :' and type. +nl_enum_colon_type = ignore # ignore/add/remove/force + +# Add or remove newline between 'struct and '{'. +nl_struct_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'union' and '{'. +nl_union_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'if' and '{'. +nl_if_brace = remove # ignore/add/remove/force + +# Add or remove newline between '}' and 'else'. +nl_brace_else = remove # ignore/add/remove/force + +# Add or remove newline between 'else if' and '{'. If set to ignore, +# nl_if_brace is used instead. +nl_elseif_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'else' and '{'. +nl_else_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'else' and 'if'. +nl_else_if = remove # ignore/add/remove/force + +# Add or remove newline before '{' opening brace +nl_before_opening_brace_func_class_def = ignore # ignore/add/remove/force + +# Add or remove newline before 'if'/'else if' closing parenthesis. +nl_before_if_closing_paren = remove # ignore/add/remove/force + +# Add or remove newline between '}' and 'finally'. +nl_brace_finally = remove # ignore/add/remove/force + +# Add or remove newline between 'finally' and '{'. +nl_finally_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'try' and '{'. +nl_try_brace = remove # ignore/add/remove/force + +# Add or remove newline between get/set and '{'. +nl_getset_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'for' and '{'. +nl_for_brace = remove # ignore/add/remove/force + +# Add or remove newline before the '{' of a 'catch' statement, as in +# 'catch (decl) {'. +nl_catch_brace = remove # ignore/add/remove/force + +# (OC) Add or remove newline before the '{' of a '@catch' statement, as in +# '@catch (decl) {'. If set to ignore, nl_catch_brace is used. +nl_oc_catch_brace = ignore # ignore/add/remove/force + +# Add or remove newline between '}' and 'catch'. +nl_brace_catch = remove # ignore/add/remove/force + +# (OC) Add or remove newline between '}' and '@catch'. If set to ignore, +# nl_brace_catch is used. +nl_oc_brace_catch = ignore # ignore/add/remove/force + +# Add or remove newline between '}' and ']'. +nl_brace_square = ignore # ignore/add/remove/force + +# Add or remove newline between '}' and ')' in a function invocation. +nl_brace_fparen = ignore # ignore/add/remove/force + +# Add or remove newline between 'while' and '{'. +nl_while_brace = remove # ignore/add/remove/force + +# (D) Add or remove newline between 'scope (x)' and '{'. +nl_scope_brace = ignore # ignore/add/remove/force + +# (D) Add or remove newline between 'unittest' and '{'. +nl_unittest_brace = ignore # ignore/add/remove/force + +# (D) Add or remove newline between 'version (x)' and '{'. +nl_version_brace = ignore # ignore/add/remove/force + +# (C#) Add or remove newline between 'using' and '{'. +nl_using_brace = ignore # ignore/add/remove/force + +# Add or remove newline between two open or close braces. Due to general +# newline/brace handling, REMOVE may not work. +nl_brace_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'do' and '{'. +nl_do_brace = remove # ignore/add/remove/force + +# Add or remove newline between '}' and 'while' of 'do' statement. +nl_brace_while = remove # ignore/add/remove/force + +# Add or remove newline between 'switch' and '{'. +nl_switch_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'synchronized' and '{'. +nl_synchronized_brace = remove # ignore/add/remove/force + +# Add a newline between ')' and '{' if the ')' is on a different line than the +# if/for/etc. +# +# Overrides nl_for_brace, nl_if_brace, nl_switch_brace, nl_while_switch and +# nl_catch_brace. +nl_multi_line_cond = false # true/false + +# Add a newline after '(' if an if/for/while/switch condition spans multiple +# lines +nl_multi_line_sparen_open = remove # ignore/add/remove/force + +# Add a newline before ')' if an if/for/while/switch condition spans multiple +# lines. Overrides nl_before_if_closing_paren if both are specified. +nl_multi_line_sparen_close = remove # ignore/add/remove/force + +# Force a newline in a define after the macro name for multi-line defines. +nl_multi_line_define = false # true/false + +# Whether to add a newline before 'case', and a blank line before a 'case' +# statement that follows a ';' or '}'. +nl_before_case = false # true/false + +# Whether to add a newline after a 'case' statement. +nl_after_case = true # true/false + +# Add or remove newline between a case ':' and '{'. +# +# Overrides nl_after_case. +nl_case_colon_brace = remove # ignore/add/remove/force + +# Add or remove newline between ')' and 'throw'. +nl_before_throw = ignore # ignore/add/remove/force + +# Add or remove newline between 'namespace' and '{'. +nl_namespace_brace = ignore # ignore/add/remove/force + +# Add or remove newline after 'template<...>' of a template class. +nl_template_class = ignore # ignore/add/remove/force + +# Add or remove newline after 'template<...>' of a template class declaration. +# +# Overrides nl_template_class. +nl_template_class_decl = ignore # ignore/add/remove/force + +# Add or remove newline after 'template<>' of a specialized class declaration. +# +# Overrides nl_template_class_decl. +nl_template_class_decl_special = ignore # ignore/add/remove/force + +# Add or remove newline after 'template<...>' of a template class definition. +# +# Overrides nl_template_class. +nl_template_class_def = ignore # ignore/add/remove/force + +# Add or remove newline after 'template<>' of a specialized class definition. +# +# Overrides nl_template_class_def. +nl_template_class_def_special = ignore # ignore/add/remove/force + +# Add or remove newline after 'template<...>' of a template function. +nl_template_func = ignore # ignore/add/remove/force + +# Add or remove newline after 'template<...>' of a template function +# declaration. +# +# Overrides nl_template_func. +nl_template_func_decl = ignore # ignore/add/remove/force + +# Add or remove newline after 'template<>' of a specialized function +# declaration. +# +# Overrides nl_template_func_decl. +nl_template_func_decl_special = ignore # ignore/add/remove/force + +# Add or remove newline after 'template<...>' of a template function +# definition. +# +# Overrides nl_template_func. +nl_template_func_def = ignore # ignore/add/remove/force + +# Add or remove newline after 'template<>' of a specialized function +# definition. +# +# Overrides nl_template_func_def. +nl_template_func_def_special = ignore # ignore/add/remove/force + +# Add or remove newline after 'template<...>' of a template variable. +nl_template_var = ignore # ignore/add/remove/force + +# Add or remove newline between 'template<...>' and 'using' of a templated +# type alias. +nl_template_using = ignore # ignore/add/remove/force + +# Add or remove newline between 'class' and '{'. +nl_class_brace = ignore # ignore/add/remove/force + +# Add or remove newline before or after (depending on pos_class_comma, +# may not be IGNORE) each',' in the base class list. +nl_class_init_args = ignore # ignore/add/remove/force + +# Add or remove newline after each ',' in the constructor member +# initialization. Related to nl_constr_colon, pos_constr_colon and +# pos_constr_comma. +nl_constr_init_args = ignore # ignore/add/remove/force + +# Add or remove newline before first element, after comma, and after last +# element, in 'enum'. +nl_enum_own_lines = force # ignore/add/remove/force + +# Add or remove newline between return type and function name in a function +# definition. +# might be modified by nl_func_leave_one_liners +nl_func_type_name = remove # ignore/add/remove/force + +# Add or remove newline between return type and function name inside a class +# definition. If set to ignore, nl_func_type_name or nl_func_proto_type_name +# is used instead. +nl_func_type_name_class = ignore # ignore/add/remove/force + +# Add or remove newline between class specification and '::' +# in 'void A::f() { }'. Only appears in separate member implementation (does +# not appear with in-line implementation). +nl_func_class_scope = ignore # ignore/add/remove/force + +# Add or remove newline between function scope and name, as in +# 'void A :: f() { }'. +nl_func_scope_name = ignore # ignore/add/remove/force + +# Add or remove newline between return type and function name in a prototype. +nl_func_proto_type_name = remove # ignore/add/remove/force + +# Add or remove newline between a function name and the opening '(' in the +# declaration. +nl_func_paren = remove # ignore/add/remove/force + +# Overrides nl_func_paren for functions with no parameters. +nl_func_paren_empty = ignore # ignore/add/remove/force + +# Add or remove newline between a function name and the opening '(' in the +# definition. +nl_func_def_paren = remove # ignore/add/remove/force + +# Overrides nl_func_def_paren for functions with no parameters. +nl_func_def_paren_empty = ignore # ignore/add/remove/force + +# Add or remove newline between a function name and the opening '(' in the +# call. +nl_func_call_paren = remove # ignore/add/remove/force + +# Overrides nl_func_call_paren for functions with no parameters. +nl_func_call_paren_empty = ignore # ignore/add/remove/force + +# Add or remove newline after '(' in a function declaration. +nl_func_decl_start = remove # ignore/add/remove/force + +# Add or remove newline after '(' in a function definition. +nl_func_def_start = remove # ignore/add/remove/force + +# Overrides nl_func_decl_start when there is only one parameter. +nl_func_decl_start_single = ignore # ignore/add/remove/force + +# Overrides nl_func_def_start when there is only one parameter. +nl_func_def_start_single = ignore # ignore/add/remove/force + +# Whether to add a newline after '(' in a function declaration if '(' and ')' +# are in different lines. If false, nl_func_decl_start is used instead. +nl_func_decl_start_multi_line = true # true/false + +# Whether to add a newline after '(' in a function definition if '(' and ')' +# are in different lines. If false, nl_func_def_start is used instead. +nl_func_def_start_multi_line = true # true/false + +# Add or remove newline after each ',' in a function declaration. +nl_func_decl_args = ignore # ignore/add/remove/force + +# Add or remove newline after each ',' in a function definition. +nl_func_def_args = ignore # ignore/add/remove/force + +# Add or remove newline after each ',' in a function call. +nl_func_call_args = ignore # ignore/add/remove/force + +# Whether to add a newline after each ',' in a function declaration if '(' +# and ')' are in different lines. If false, nl_func_decl_args is used instead. +nl_func_decl_args_multi_line = false # true/false + +# Whether to add a newline after each ',' in a function definition if '(' +# and ')' are in different lines. If false, nl_func_def_args is used instead. +nl_func_def_args_multi_line = false # true/false + +# Add or remove newline before the ')' in a function declaration. +nl_func_decl_end = ignore # ignore/add/remove/force + +# Add or remove newline before the ')' in a function definition. +nl_func_def_end = ignore # ignore/add/remove/force + +# Overrides nl_func_decl_end when there is only one parameter. +nl_func_decl_end_single = ignore # ignore/add/remove/force + +# Overrides nl_func_def_end when there is only one parameter. +nl_func_def_end_single = ignore # ignore/add/remove/force + +# Whether to add a newline before ')' in a function declaration if '(' and ')' +# are in different lines. If false, nl_func_decl_end is used instead. +nl_func_decl_end_multi_line = false # true/false + +# Whether to add a newline before ')' in a function definition if '(' and ')' +# are in different lines. If false, nl_func_def_end is used instead. +nl_func_def_end_multi_line = false # true/false + +# Add or remove newline between '()' in a function declaration. +nl_func_decl_empty = ignore # ignore/add/remove/force + +# Add or remove newline between '()' in a function definition. +nl_func_def_empty = remove # ignore/add/remove/force + +# Add or remove newline between '()' in a function call. +nl_func_call_empty = remove # ignore/add/remove/force + +# Whether to add a newline after '(' in a function call, +# has preference over nl_func_call_start_multi_line. +nl_func_call_start = ignore # ignore/add/remove/force + +# Whether to add a newline before ')' in a function call. +nl_func_call_end = ignore # ignore/add/remove/force + +# Whether to add a newline after '(' in a function call if '(' and ')' are in +# different lines. +nl_func_call_start_multi_line = false # true/false + +# Whether to add a newline after each ',' in a function call if '(' and ')' +# are in different lines. +nl_func_call_args_multi_line = false # true/false + +# Whether to add a newline before ')' in a function call if '(' and ')' are in +# different lines. +nl_func_call_end_multi_line = false # true/false + +# Whether to respect nl_func_call_XXX option incase of closure args. +nl_func_call_args_multi_line_ignore_closures = false # true/false + +# Whether to add a newline after '<' of a template parameter list. +nl_template_start = false # true/false + +# Whether to add a newline after each ',' in a template parameter list. +nl_template_args = false # true/false + +# Whether to add a newline before '>' of a template parameter list. +nl_template_end = false # true/false + +# (OC) Whether to put each Objective-C message parameter on a separate line. +# See nl_oc_msg_leave_one_liner. +nl_oc_msg_args = false # true/false + +# Add or remove newline between function signature and '{'. +nl_fdef_brace = remove # ignore/add/remove/force + +# Add or remove newline between function signature and '{', +# if signature ends with ')'. Overrides nl_fdef_brace. +nl_fdef_brace_cond = ignore # ignore/add/remove/force + +# Add or remove newline between C++11 lambda signature and '{'. +nl_cpp_ldef_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'return' and the return expression. +nl_return_expr = remove # ignore/add/remove/force + +# Whether to add a newline after semicolons, except in 'for' statements. +nl_after_semicolon = true # true/false + +# (Java) Add or remove newline between the ')' and '{{' of the double brace +# initializer. +nl_paren_dbrace_open = ignore # ignore/add/remove/force + +# Whether to add a newline after the type in an unnamed temporary +# direct-list-initialization. +nl_type_brace_init_lst = ignore # ignore/add/remove/force + +# Whether to add a newline after the open brace in an unnamed temporary +# direct-list-initialization. +nl_type_brace_init_lst_open = ignore # ignore/add/remove/force + +# Whether to add a newline before the close brace in an unnamed temporary +# direct-list-initialization. +nl_type_brace_init_lst_close = ignore # ignore/add/remove/force + +# Whether to add a newline after '{'. This also adds a newline before the +# matching '}'. +nl_after_brace_open = true # true/false + +# Whether to add a newline between the open brace and a trailing single-line +# comment. Requires nl_after_brace_open=true. +nl_after_brace_open_cmt = false # true/false + +# Whether to add a newline after a virtual brace open with a non-empty body. +# These occur in un-braced if/while/do/for statement bodies. +nl_after_vbrace_open = false # true/false + +# Whether to add a newline after a virtual brace open with an empty body. +# These occur in un-braced if/while/do/for statement bodies. +nl_after_vbrace_open_empty = false # true/false + +# Whether to add a newline after '}'. Does not apply if followed by a +# necessary ';'. +nl_after_brace_close = false # true/false + +# Whether to add a newline after a virtual brace close, +# as in 'if (foo) a++; return;'. +nl_after_vbrace_close = false # true/false + +# Add or remove newline between the close brace and identifier, +# as in 'struct { int a; } b;'. Affects enumerations, unions and +# structures. If set to ignore, uses nl_after_brace_close. +nl_brace_struct_var = remove # ignore/add/remove/force + +# Whether to alter newlines in '#define' macros. +nl_define_macro = false # true/false + +# Whether to alter newlines between consecutive parenthesis closes. The number +# of closing parentheses in a line will depend on respective open parenthesis +# lines. +nl_squeeze_paren_close = false # true/false + +# Whether to remove blanks after '#ifxx' and '#elxx', or before '#elxx' and +# '#endif'. Does not affect top-level #ifdefs. +nl_squeeze_ifdef = false # true/false + +# Makes the nl_squeeze_ifdef option affect the top-level #ifdefs as well. +nl_squeeze_ifdef_top_level = false # true/false + +# Add or remove blank line before 'if'. +nl_before_if = ignore # ignore/add/remove/force + +# Add or remove blank line after 'if' statement. Add/Force work only if the +# next token is not a closing brace. +nl_after_if = ignore # ignore/add/remove/force + +# Add or remove blank line before 'for'. +nl_before_for = ignore # ignore/add/remove/force + +# Add or remove blank line after 'for' statement. +nl_after_for = ignore # ignore/add/remove/force + +# Add or remove blank line before 'while'. +nl_before_while = ignore # ignore/add/remove/force + +# Add or remove blank line after 'while' statement. +nl_after_while = ignore # ignore/add/remove/force + +# Add or remove blank line before 'switch'. +nl_before_switch = ignore # ignore/add/remove/force + +# Add or remove blank line after 'switch' statement. +nl_after_switch = ignore # ignore/add/remove/force + +# Add or remove blank line before 'synchronized'. +nl_before_synchronized = ignore # ignore/add/remove/force + +# Add or remove blank line after 'synchronized' statement. +nl_after_synchronized = ignore # ignore/add/remove/force + +# Add or remove blank line before 'do'. +nl_before_do = ignore # ignore/add/remove/force + +# Add or remove blank line after 'do/while' statement. +nl_after_do = ignore # ignore/add/remove/force + +# Whether to put a blank line before 'return' statements, unless after an open +# brace. +nl_before_return = false # true/false + +# Whether to put a blank line after 'return' statements, unless followed by a +# close brace. +nl_after_return = false # true/false + +# Whether to put a blank line before a member '.' or '->' operators. +nl_before_member = ignore # ignore/add/remove/force + +# (Java) Whether to put a blank line after a member '.' or '->' operators. +nl_after_member = ignore # ignore/add/remove/force + +# Whether to double-space commented-entries in 'struct'/'union'/'enum'. +nl_ds_struct_enum_cmt = false # true/false + +# Whether to force a newline before '}' of a 'struct'/'union'/'enum'. +# (Lower priority than eat_blanks_before_close_brace.) +nl_ds_struct_enum_close_brace = false # true/false + +# Add or remove newline before or after (depending on pos_class_colon) a class +# colon, as in 'class Foo : public Bar'. +nl_class_colon = ignore # ignore/add/remove/force + +# Add or remove newline around a class constructor colon. The exact position +# depends on nl_constr_init_args, pos_constr_colon and pos_constr_comma. +nl_constr_colon = ignore # ignore/add/remove/force + +# Whether to collapse a two-line namespace, like 'namespace foo\n{ decl; }' +# into a single line. If true, prevents other brace newline rules from turning +# such code into four lines. +nl_namespace_two_to_one_liner = false # true/false + +# Whether to remove a newline in simple unbraced if statements, turning them +# into one-liners, as in 'if(b)\n i++;' => 'if(b) i++;'. +nl_create_if_one_liner = false # true/false + +# Whether to remove a newline in simple unbraced for statements, turning them +# into one-liners, as in 'for (...)\n stmt;' => 'for (...) stmt;'. +nl_create_for_one_liner = false # true/false + +# Whether to remove a newline in simple unbraced while statements, turning +# them into one-liners, as in 'while (expr)\n stmt;' => 'while (expr) stmt;'. +nl_create_while_one_liner = false # true/false + +# Whether to collapse a function definition whose body (not counting braces) +# is only one line so that the entire definition (prototype, braces, body) is +# a single line. +nl_create_func_def_one_liner = false # true/false + +# Whether to collapse a function definition whose body (not counting braces) +# is only one line so that the entire definition (prototype, braces, body) is +# a single line. +nl_create_list_one_liner = false # true/false + +# Whether to split one-line simple unbraced if statements into two lines by +# adding a newline, as in 'if(b) i++;'. +nl_split_if_one_liner = false # true/false + +# Whether to split one-line simple unbraced for statements into two lines by +# adding a newline, as in 'for (...) stmt;'. +nl_split_for_one_liner = false # true/false + +# Whether to split one-line simple unbraced while statements into two lines by +# adding a newline, as in 'while (expr) stmt;'. +nl_split_while_one_liner = false # true/false + +# Don't add a newline before a cpp-comment in a parameter list of a function +# call. +donot_add_nl_before_cpp_comment = false # true/false + +# +# Blank line options +# + +# The maximum number of consecutive newlines (3 = 2 blank lines). +nl_max = 3 # unsigned number + +# The maximum number of consecutive newlines in a function. +nl_max_blank_in_func = 3 # unsigned number + +# The number of newlines inside an empty function body. +# This option overrides eat_blanks_after_open_brace and +# eat_blanks_before_close_brace, but is ignored when +# nl_collapse_empty_body=true +nl_inside_empty_func = 0 # unsigned number + +# The number of newlines before a function prototype. +nl_before_func_body_proto = 0 # unsigned number + +# The number of newlines before a multi-line function definition. Where +# applicable, this option is overridden with eat_blanks_after_open_brace=true +nl_before_func_body_def = 2 # unsigned number + +# The number of newlines before a class constructor/destructor prototype. +nl_before_func_class_proto = 0 # unsigned number + +# The number of newlines before a class constructor/destructor definition. +nl_before_func_class_def = 0 # unsigned number + +# The number of newlines after a function prototype. +nl_after_func_proto = 0 # unsigned number + +# The number of newlines after a function prototype, if not followed by +# another function prototype. +nl_after_func_proto_group = 2 # unsigned number + +# The number of newlines after a class constructor/destructor prototype. +nl_after_func_class_proto = 0 # unsigned number + +# The number of newlines after a class constructor/destructor prototype, +# if not followed by another constructor/destructor prototype. +nl_after_func_class_proto_group = 0 # unsigned number + +# Whether one-line method definitions inside a class body should be treated +# as if they were prototypes for the purposes of adding newlines. +# +# Requires nl_class_leave_one_liners=true. Overrides nl_before_func_body_def +# and nl_before_func_class_def for one-liners. +nl_class_leave_one_liner_groups = false # true/false + +# The number of newlines after '}' of a multi-line function body. +nl_after_func_body = 2 # unsigned number + +# The number of newlines after '}' of a multi-line function body in a class +# declaration. Also affects class constructors/destructors. +# +# Overrides nl_after_func_body. +nl_after_func_body_class = 0 # unsigned number + +# The number of newlines after '}' of a single line function body. Also +# affects class constructors/destructors. +# +# Overrides nl_after_func_body and nl_after_func_body_class. +nl_after_func_body_one_liner = 0 # unsigned number + +# The number of blank lines after a block of variable definitions at the top +# of a function body. +# +# 0: No change (default). +nl_func_var_def_blk = 0 # unsigned number + +# The number of newlines before a block of typedefs. If nl_after_access_spec +# is non-zero, that option takes precedence. +# +# 0: No change (default). +nl_typedef_blk_start = 0 # unsigned number + +# The number of newlines after a block of typedefs. +# +# 0: No change (default). +nl_typedef_blk_end = 0 # unsigned number + +# The maximum number of consecutive newlines within a block of typedefs. +# +# 0: No change (default). +nl_typedef_blk_in = 0 # unsigned number + +# The number of newlines before a block of variable definitions not at the top +# of a function body. If nl_after_access_spec is non-zero, that option takes +# precedence. +# +# 0: No change (default). +nl_var_def_blk_start = 0 # unsigned number + +# The number of newlines after a block of variable definitions not at the top +# of a function body. +# +# 0: No change (default). +nl_var_def_blk_end = 0 # unsigned number + +# The maximum number of consecutive newlines within a block of variable +# definitions. +# +# 0: No change (default). +nl_var_def_blk_in = 0 # unsigned number + +# The minimum number of newlines before a multi-line comment. +# Doesn't apply if after a brace open or another multi-line comment. +nl_before_block_comment = 0 # unsigned number + +# The minimum number of newlines before a single-line C comment. +# Doesn't apply if after a brace open or other single-line C comments. +nl_before_c_comment = 0 # unsigned number + +# The minimum number of newlines before a CPP comment. +# Doesn't apply if after a brace open or other CPP comments. +nl_before_cpp_comment = 0 # unsigned number + +# Whether to force a newline after a multi-line comment. +nl_after_multiline_comment = false # true/false + +# Whether to force a newline after a label's colon. +nl_after_label_colon = true # true/false + +# The number of newlines after '}' or ';' of a struct/enum/union definition. +nl_after_struct = 0 # unsigned number + +# The number of newlines before a class definition. +nl_before_class = 0 # unsigned number + +# The number of newlines after '}' or ';' of a class definition. +nl_after_class = 0 # unsigned number + +# The number of newlines before a namespace. +nl_before_namespace = 0 # unsigned number + +# The number of newlines after '{' of a namespace. This also adds newlines +# before the matching '}'. +# +# 0: Apply eat_blanks_after_open_brace or eat_blanks_before_close_brace if +# applicable, otherwise no change. +# +# Overrides eat_blanks_after_open_brace and eat_blanks_before_close_brace. +nl_inside_namespace = 0 # unsigned number + +# The number of newlines after '}' of a namespace. +nl_after_namespace = 0 # unsigned number + +# The number of newlines before an access specifier label. This also includes +# the Qt-specific 'signals:' and 'slots:'. Will not change the newline count +# if after a brace open. +# +# 0: No change (default). +nl_before_access_spec = 0 # unsigned number + +# The number of newlines after an access specifier label. This also includes +# the Qt-specific 'signals:' and 'slots:'. Will not change the newline count +# if after a brace open. +# +# 0: No change (default). +# +# Overrides nl_typedef_blk_start and nl_var_def_blk_start. +nl_after_access_spec = 0 # unsigned number + +# The number of newlines between a function definition and the function +# comment, as in '// comment\n void foo() {...}'. +# +# 0: No change (default). +nl_comment_func_def = 0 # unsigned number + +# The number of newlines after a try-catch-finally block that isn't followed +# by a brace close. +# +# 0: No change (default). +nl_after_try_catch_finally = 0 # unsigned number + +# (C#) The number of newlines before and after a property, indexer or event +# declaration. +# +# 0: No change (default). +nl_around_cs_property = 0 # unsigned number + +# (C#) The number of newlines between the get/set/add/remove handlers. +# +# 0: No change (default). +nl_between_get_set = 0 # unsigned number + +# (C#) Add or remove newline between property and the '{'. +nl_property_brace = ignore # ignore/add/remove/force + +# Whether to remove blank lines after '{'. +eat_blanks_after_open_brace = false # true/false + +# Whether to remove blank lines before '}'. +eat_blanks_before_close_brace = true # true/false + +# How aggressively to remove extra newlines not in preprocessor. +# +# 0: No change (default) +# 1: Remove most newlines not handled by other config +# 2: Remove all newlines and reformat completely by config +nl_remove_extra_newlines = 0 # unsigned number + +# (Java) Add or remove newline after an annotation statement. Only affects +# annotations that are after a newline. +nl_after_annotation = ignore # ignore/add/remove/force + +# (Java) Add or remove newline between two annotations. +nl_between_annotation = ignore # ignore/add/remove/force + +# The number of newlines before a whole-file #ifdef. +# +# 0: No change (default). +nl_before_whole_file_ifdef = 0 # unsigned number + +# The number of newlines after a whole-file #ifdef. +# +# 0: No change (default). +nl_after_whole_file_ifdef = 0 # unsigned number + +# The number of newlines before a whole-file #endif. +# +# 0: No change (default). +nl_before_whole_file_endif = 0 # unsigned number + +# The number of newlines after a whole-file #endif. +# +# 0: No change (default). +nl_after_whole_file_endif = 0 # unsigned number + +# +# Positioning options +# + +# The position of arithmetic operators in wrapped expressions. +pos_arith = lead # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force + +# The position of assignment in wrapped expressions. Do not affect '=' +# followed by '{'. +pos_assign = lead # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force + +# The position of Boolean operators in wrapped expressions. +pos_bool = lead # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force + +# The position of comparison operators in wrapped expressions. +pos_compare = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force + +# The position of conditional operators, as in the '?' and ':' of +# 'expr ? stmt : stmt', in wrapped expressions. +pos_conditional = lead # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force + +# The position of the comma in wrapped expressions. +pos_comma = trail # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force + +# The position of the comma in enum entries. +pos_enum_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force + +# The position of the comma in the base class list if there is more than one +# line. Affects nl_class_init_args. +pos_class_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force + +# The position of the comma in the constructor initialization list. +# Related to nl_constr_colon, nl_constr_init_args and pos_constr_colon. +pos_constr_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force + +# The position of trailing/leading class colon, between class and base class +# list. Affects nl_class_colon. +pos_class_colon = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force + +# The position of colons between constructor and member initialization. +# Related to nl_constr_colon, nl_constr_init_args and pos_constr_comma. +pos_constr_colon = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force + +# The position of shift operators in wrapped expressions. +pos_shift = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force + +# +# Line splitting options +# + +# Try to limit code width to N columns. +code_width = 120 # unsigned number + +# Whether to fully split long 'for' statements at semi-colons. +ls_for_split_full = true # true/false + +# Whether to fully split long function prototypes/calls at commas. +# The option ls_code_width has priority over the option ls_func_split_full. +ls_func_split_full = true # true/false + +# Whether to split lines as close to code_width as possible and ignore some +# groupings. +# The option ls_code_width has priority over the option ls_func_split_full. +ls_code_width = false # true/false + +# +# Code alignment options (not left column spaces/tabs) +# + +# Whether to keep non-indenting tabs. +align_keep_tabs = false # true/false + +# Whether to use tabs for aligning. +align_with_tabs = false # true/false + +# Whether to bump out to the next tab when aligning. +align_on_tabstop = false # true/false + +# Whether to right-align numbers. +align_number_right = false # true/false + +# Whether to keep whitespace not required for alignment. +align_keep_extra_space = false # true/false + +# Whether to align variable definitions in prototypes and functions. +align_func_params = true # true/false + +# The span for aligning parameter definitions in function on parameter name. +# +# 0: Don't align (default). +align_func_params_span = 0 # unsigned number + +# The threshold for aligning function parameter definitions. +# Use a negative number for absolute thresholds. +# +# 0: No limit (default). +align_func_params_thresh = 0 # number + +# The gap for aligning function parameter definitions. +align_func_params_gap = 0 # unsigned number + +# The span for aligning constructor value. +# +# 0: Don't align (default). +align_constr_value_span = 0 # unsigned number + +# The threshold for aligning constructor value. +# Use a negative number for absolute thresholds. +# +# 0: No limit (default). +align_constr_value_thresh = 0 # number + +# The gap for aligning constructor value. +align_constr_value_gap = 0 # unsigned number + +# Whether to align parameters in single-line functions that have the same +# name. The function names must already be aligned with each other. +align_same_func_call_params = false # true/false + +# The span for aligning function-call parameters for single line functions. +# +# 0: Don't align (default). +align_same_func_call_params_span = 0 # unsigned number + +# The threshold for aligning function-call parameters for single line +# functions. +# Use a negative number for absolute thresholds. +# +# 0: No limit (default). +align_same_func_call_params_thresh = 0 # number + +# The span for aligning variable definitions. +# +# 0: Don't align (default). +align_var_def_span = 0 # unsigned number + +# How to consider (or treat) the '*' in the alignment of variable definitions. +# +# 0: Part of the type 'void * foo;' (default) +# 1: Part of the variable 'void *foo;' +# 2: Dangling 'void *foo;' +# Dangling: the '*' will not be taken into account when aligning. +align_var_def_star_style = 2 # unsigned number + +# How to consider (or treat) the '&' in the alignment of variable definitions. +# +# 0: Part of the type 'long & foo;' (default) +# 1: Part of the variable 'long &foo;' +# 2: Dangling 'long &foo;' +# Dangling: the '&' will not be taken into account when aligning. +align_var_def_amp_style = 1 # unsigned number + +# The threshold for aligning variable definitions. +# Use a negative number for absolute thresholds. +# +# 0: No limit (default). +align_var_def_thresh = 0 # number + +# The gap for aligning variable definitions. +align_var_def_gap = 0 # unsigned number + +# Whether to align the colon in struct bit fields. +align_var_def_colon = false # true/false + +# The gap for aligning the colon in struct bit fields. +align_var_def_colon_gap = 0 # unsigned number + +# Whether to align any attribute after the variable name. +align_var_def_attribute = false # true/false + +# Whether to align inline struct/enum/union variable definitions. +align_var_def_inline = false # true/false + +# The span for aligning on '=' in assignments. +# +# 0: Don't align (default). +align_assign_span = 0 # unsigned number + +# The span for aligning on '=' in function prototype modifier. +# +# 0: Don't align (default). +align_assign_func_proto_span = 0 # unsigned number + +# The threshold for aligning on '=' in assignments. +# Use a negative number for absolute thresholds. +# +# 0: No limit (default). +align_assign_thresh = 0 # number + +# How to apply align_assign_span to function declaration "assignments", i.e. +# 'virtual void foo() = 0' or '~foo() = {default|delete}'. +# +# 0: Align with other assignments (default) +# 1: Align with each other, ignoring regular assignments +# 2: Don't align +align_assign_decl_func = 0 # unsigned number + +# The span for aligning on '=' in enums. +# +# 0: Don't align (default). +align_enum_equ_span = 1 # unsigned number + +# The threshold for aligning on '=' in enums. +# Use a negative number for absolute thresholds. +# +# 0: no limit (default). +align_enum_equ_thresh = 0 # number + +# The span for aligning class member definitions. +# +# 0: Don't align (default). +align_var_class_span = 0 # unsigned number + +# The threshold for aligning class member definitions. +# Use a negative number for absolute thresholds. +# +# 0: No limit (default). +align_var_class_thresh = 0 # number + +# The gap for aligning class member definitions. +align_var_class_gap = 0 # unsigned number + +# The span for aligning struct/union member definitions. +# +# 0: Don't align (default). +align_var_struct_span = 1 # unsigned number + +# The threshold for aligning struct/union member definitions. +# Use a negative number for absolute thresholds. +# +# 0: No limit (default). +align_var_struct_thresh = -6 # number + +# The gap for aligning struct/union member definitions. +align_var_struct_gap = 0 # unsigned number + +# The span for aligning struct initializer values. +# +# 0: Don't align (default). +align_struct_init_span = 1 # unsigned number + +# The span for aligning single-line typedefs. +# +# 0: Don't align (default). +align_typedef_span = 1 # unsigned number + +# The minimum space between the type and the synonym of a typedef. +align_typedef_gap = 0 # unsigned number + +# How to align typedef'd functions with other typedefs. +# +# 0: Don't mix them at all (default) +# 1: Align the open parenthesis with the types +# 2: Align the function type name with the other type names +align_typedef_func = 0 # unsigned number + +# How to consider (or treat) the '*' in the alignment of typedefs. +# +# 0: Part of the typedef type, 'typedef int * pint;' (default) +# 1: Part of type name: 'typedef int *pint;' +# 2: Dangling: 'typedef int *pint;' +# Dangling: the '*' will not be taken into account when aligning. +align_typedef_star_style = 2 # unsigned number + +# How to consider (or treat) the '&' in the alignment of typedefs. +# +# 0: Part of the typedef type, 'typedef int & intref;' (default) +# 1: Part of type name: 'typedef int &intref;' +# 2: Dangling: 'typedef int &intref;' +# Dangling: the '&' will not be taken into account when aligning. +align_typedef_amp_style = 1 # unsigned number + +# The span for aligning comments that end lines. +# +# 0: Don't align (default). +align_right_cmt_span = 2 # unsigned number + +# Minimum number of columns between preceding text and a trailing comment in +# order for the comment to qualify for being aligned. Must be non-zero to have +# an effect. +align_right_cmt_gap = 0 # unsigned number + +# If aligning comments, whether to mix with comments after '}' and #endif with +# less than three spaces before the comment. +align_right_cmt_mix = false # true/false + +# Whether to only align trailing comments that are at the same brace level. +align_right_cmt_same_level = false # true/false + +# Minimum column at which to align trailing comments. Comments which are +# aligned beyond this column, but which can be aligned in a lesser column, +# may be "pulled in". +# +# 0: Ignore (default). +align_right_cmt_at_col = 0 # unsigned number + +# The span for aligning function prototypes. +# +# 0: Don't align (default). +align_func_proto_span = 0 # unsigned number + +# The threshold for aligning function prototypes. +# Use a negative number for absolute thresholds. +# +# 0: No limit (default). +align_func_proto_thresh = 0 # number + +# Minimum gap between the return type and the function name. +align_func_proto_gap = 0 # unsigned number + +# Whether to align function prototypes on the 'operator' keyword instead of +# what follows. +align_on_operator = false # true/false + +# Whether to mix aligning prototype and variable declarations. If true, +# align_var_def_XXX options are used instead of align_func_proto_XXX options. +align_mix_var_proto = false # true/false + +# Whether to align single-line functions with function prototypes. +# Uses align_func_proto_span. +align_single_line_func = false # true/false + +# Whether to align the open brace of single-line functions. +# Requires align_single_line_func=true. Uses align_func_proto_span. +align_single_line_brace = false # true/false + +# Gap for align_single_line_brace. +align_single_line_brace_gap = 0 # unsigned number + +# (OC) The span for aligning Objective-C message specifications. +# +# 0: Don't align (default). +align_oc_msg_spec_span = 0 # unsigned number + +# Whether to align macros wrapped with a backslash and a newline. This will +# not work right if the macro contains a multi-line comment. +align_nl_cont = false # true/false + +# Whether to align macro functions and variables together. +align_pp_define_together = false # true/false + +# The span for aligning on '#define' bodies. +# +# =0: Don't align (default) +# >0: Number of lines (including comments) between blocks +align_pp_define_span = 1 # unsigned number + +# The minimum space between label and value of a preprocessor define. +align_pp_define_gap = 0 # unsigned number + +# Whether to align lines that start with '<<' with previous '<<'. +# +# Default: true +align_left_shift = true # true/false + +# Whether to align comma-separated statements following '<<' (as used to +# initialize Eigen matrices). +align_eigen_comma_init = false # true/false + +# Whether to align text after 'asm volatile ()' colons. +align_asm_colon = false # true/false + +# (OC) Span for aligning parameters in an Objective-C message call +# on the ':'. +# +# 0: Don't align. +align_oc_msg_colon_span = 0 # unsigned number + +# (OC) Whether to always align with the first parameter, even if it is too +# short. +align_oc_msg_colon_first = false # true/false + +# (OC) Whether to align parameters in an Objective-C '+' or '-' declaration +# on the ':'. +align_oc_decl_colon = false # true/false + +# (OC) Whether to not align parameters in an Objectve-C message call if first +# colon is not on next line of the message call (the same way Xcode does +# aligment) +align_oc_msg_colon_xcode_like = false # true/false + +# +# Comment modification options +# + +# Try to wrap comments at N columns. +cmt_width = 120 # unsigned number + +# How to reflow comments. +# +# 0: No reflowing (apart from the line wrapping due to cmt_width) (default) +# 1: No touching at all +# 2: Full reflow (enable cmt_indent_multi for indent with line wrapping due to cmt_width) +cmt_reflow_mode = 0 # unsigned number + +# Path to a file that contains regular expressions describing patterns for which the +# end of one line and the beginning of the next will be folded into the same sentence +# or paragraph during full comment reflow. The regular expressions are described using +# ECMAScript syntax. The syntax for this specification is as follows, where "..." indicates +# the custom regular expression and "n" indicates the nth end_of_prev_line_regex +# and beg_of_next_line_regex regular expression pair: +# +# end_of_prev_line_regex[1] = "...$" +# beg_of_next_line_regex[1] = "^..." +# end_of_prev_line_regex[2] = "...$" +# beg_of_next_line_regex[2] = "^..." +# . +# . +# . +# end_of_prev_line_regex[n] = "...$" +# beg_of_next_line_regex[n] = "^..." +# +# Note that use of this option overrides the default reflow fold regular expressions, +# which are internally defined as follows: +# +# end_of_prev_line_regex[1] = "[\w,\]\)]$" +# beg_of_next_line_regex[1] = "^[\w,\[\(]" +# end_of_prev_line_regex[2] = "\.$" +# beg_of_next_line_regex[2] = "^[A-Z]" +cmt_reflow_fold_regex_file = "" # string + +# Whether to convert all tabs to spaces in comments. If false, tabs in +# comments are left alone, unless used for indenting. +cmt_convert_tab_to_spaces = false # true/false + +# Whether to apply changes to multi-line comments, including cmt_width, +# keyword substitution and leading chars. +# +# Default: true +cmt_indent_multi = true # true/false + +# Whether to align doxygen javadoc-style tags ('@param', '@return', etc.) +# and corresponding fields such that groups of consecutive block tags, +# parameter names, and descriptions align with one another. Overrides that +# which is specified by the cmt_sp_after_star_cont. If cmt_width > 0, it may +# be necessary to enable cmt_indent_multi and set cmt_reflow_mode = 2 +# in order to achieve the desired alignment for line-wrapping. +cmt_align_doxygen_javadoc_tags = false # true/false + +# The number of spaces to insert after the star and before doxygen +# javadoc-style tags (@param, @return, etc). Requires enabling +# cmt_align_doxygen_javadoc_tags. Overrides that which is specified by the +# cmt_sp_after_star_cont. +# +# Default: 1 +cmt_sp_before_doxygen_javadoc_tags = 1 # unsigned number + +# Whether to group c-comments that look like they are in a block. +cmt_c_group = false # true/false + +# Whether to put an empty '/*' on the first line of the combined c-comment. +cmt_c_nl_start = false # true/false + +# Whether to add a newline before the closing '*/' of the combined c-comment. +cmt_c_nl_end = false # true/false + +# Whether to change cpp-comments into c-comments. +cmt_cpp_to_c = false # true/false + +# Whether to group cpp-comments that look like they are in a block. Only +# meaningful if cmt_cpp_to_c=true. +cmt_cpp_group = false # true/false + +# Whether to put an empty '/*' on the first line of the combined cpp-comment +# when converting to a c-comment. +# +# Requires cmt_cpp_to_c=true and cmt_cpp_group=true. +cmt_cpp_nl_start = false # true/false + +# Whether to add a newline before the closing '*/' of the combined cpp-comment +# when converting to a c-comment. +# +# Requires cmt_cpp_to_c=true and cmt_cpp_group=true. +cmt_cpp_nl_end = false # true/false + +# Whether to put a star on subsequent comment lines. +cmt_star_cont = false # true/false + +# The number of spaces to insert at the start of subsequent comment lines. +cmt_sp_before_star_cont = 0 # unsigned number + +# The number of spaces to insert after the star on subsequent comment lines. +cmt_sp_after_star_cont = 0 # unsigned number + +# For multi-line comments with a '*' lead, remove leading spaces if the first +# and last lines of the comment are the same length. +# +# Default: true +cmt_multi_check_last = true # true/false + +# For multi-line comments with a '*' lead, remove leading spaces if the first +# and last lines of the comment are the same length AND if the length is +# bigger as the first_len minimum. +# +# Default: 4 +cmt_multi_first_len_minimum = 4 # unsigned number + +# Path to a file that contains text to insert at the beginning of a file if +# the file doesn't start with a C/C++ comment. If the inserted text contains +# '$(filename)', that will be replaced with the current file's name. +cmt_insert_file_header = "" # string + +# Path to a file that contains text to insert at the end of a file if the +# file doesn't end with a C/C++ comment. If the inserted text contains +# '$(filename)', that will be replaced with the current file's name. +cmt_insert_file_footer = "" # string + +# Path to a file that contains text to insert before a function definition if +# the function isn't preceded by a C/C++ comment. If the inserted text +# contains '$(function)', '$(javaparam)' or '$(fclass)', these will be +# replaced with, respectively, the name of the function, the javadoc '@param' +# and '@return' stuff, or the name of the class to which the member function +# belongs. +cmt_insert_func_header = "" # string + +# Path to a file that contains text to insert before a class if the class +# isn't preceded by a C/C++ comment. If the inserted text contains '$(class)', +# that will be replaced with the class name. +cmt_insert_class_header = "" # string + +# Path to a file that contains text to insert before an Objective-C message +# specification, if the method isn't preceded by a C/C++ comment. If the +# inserted text contains '$(message)' or '$(javaparam)', these will be +# replaced with, respectively, the name of the function, or the javadoc +# '@param' and '@return' stuff. +cmt_insert_oc_msg_header = "" # string + +# Whether a comment should be inserted if a preprocessor is encountered when +# stepping backwards from a function name. +# +# Applies to cmt_insert_oc_msg_header, cmt_insert_func_header and +# cmt_insert_class_header. +cmt_insert_before_preproc = false # true/false + +# Whether a comment should be inserted if a function is declared inline to a +# class definition. +# +# Applies to cmt_insert_func_header. +# +# Default: true +cmt_insert_before_inlines = true # true/false + +# Whether a comment should be inserted if the function is a class constructor +# or destructor. +# +# Applies to cmt_insert_func_header. +cmt_insert_before_ctor_dtor = false # true/false + +# +# Code modifying options (non-whitespace) +# + +# Add or remove braces on a single-line 'do' statement. +mod_full_brace_do = force # ignore/add/remove/force + +# Add or remove braces on a single-line 'for' statement. +mod_full_brace_for = ignore # ignore/add/remove/force + +# (Pawn) Add or remove braces on a single-line function definition. +mod_full_brace_function = ignore # ignore/add/remove/force + +# Add or remove braces on a single-line 'if' statement. Braces will not be +# removed if the braced statement contains an 'else'. +mod_full_brace_if = force # ignore/add/remove/force + +# Whether to enforce that all blocks of an 'if'/'else if'/'else' chain either +# have, or do not have, braces. If true, braces will be added if any block +# needs braces, and will only be removed if they can be removed from all +# blocks. +# +# Overrides mod_full_brace_if. +mod_full_brace_if_chain = false # true/false + +# Whether to add braces to all blocks of an 'if'/'else if'/'else' chain. +# If true, mod_full_brace_if_chain will only remove braces from an 'if' that +# does not have an 'else if' or 'else'. +mod_full_brace_if_chain_only = false # true/false + +# Add or remove braces on single-line 'while' statement. +mod_full_brace_while = ignore # ignore/add/remove/force + +# Add or remove braces on single-line 'using ()' statement. +mod_full_brace_using = ignore # ignore/add/remove/force + +# Don't remove braces around statements that span N newlines +mod_full_brace_nl = 0 # unsigned number + +# Whether to prevent removal of braces from 'if'/'for'/'while'/etc. blocks +# which span multiple lines. +# +# Affects: +# mod_full_brace_for +# mod_full_brace_if +# mod_full_brace_if_chain +# mod_full_brace_if_chain_only +# mod_full_brace_while +# mod_full_brace_using +# +# Does not affect: +# mod_full_brace_do +# mod_full_brace_function +mod_full_brace_nl_block_rem_mlcond = false # true/false + +# Add or remove unnecessary parenthesis on 'return' statement. +mod_paren_on_return = ignore # ignore/add/remove/force + +# (Pawn) Whether to change optional semicolons to real semicolons. +mod_pawn_semicolon = false # true/false + +# Whether to fully parenthesize Boolean expressions in 'while' and 'if' +# statement, as in 'if (a && b > c)' => 'if (a && (b > c))'. +mod_full_paren_if_bool = false # true/false + +# Whether to remove superfluous semicolons. +mod_remove_extra_semicolon = false # true/false + +# If a function body exceeds the specified number of newlines and doesn't have +# a comment after the close brace, a comment will be added. +mod_add_long_function_closebrace_comment = 0 # unsigned number + +# If a namespace body exceeds the specified number of newlines and doesn't +# have a comment after the close brace, a comment will be added. +mod_add_long_namespace_closebrace_comment = 0 # unsigned number + +# If a class body exceeds the specified number of newlines and doesn't have a +# comment after the close brace, a comment will be added. +mod_add_long_class_closebrace_comment = 0 # unsigned number + +# If a switch body exceeds the specified number of newlines and doesn't have a +# comment after the close brace, a comment will be added. +mod_add_long_switch_closebrace_comment = 0 # unsigned number + +# If an #ifdef body exceeds the specified number of newlines and doesn't have +# a comment after the #endif, a comment will be added. +mod_add_long_ifdef_endif_comment = 0 # unsigned number + +# If an #ifdef or #else body exceeds the specified number of newlines and +# doesn't have a comment after the #else, a comment will be added. +mod_add_long_ifdef_else_comment = 0 # unsigned number + +# Whether to take care of the case by the mod_sort_xx options. +mod_sort_case_sensitive = false # true/false + +# Whether to sort consecutive single-line 'import' statements. +mod_sort_import = false # true/false + +# (C#) Whether to sort consecutive single-line 'using' statements. +mod_sort_using = false # true/false + +# Whether to sort consecutive single-line '#include' statements (C/C++) and +# '#import' statements (Objective-C). Be aware that this has the potential to +# break your code if your includes/imports have ordering dependencies. +mod_sort_include = false # true/false + +# Whether to prioritize '#include' and '#import' statements that contain +# filename without extension when sorting is enabled. +mod_sort_incl_import_prioritize_filename = false # true/false + +# Whether to prioritize '#include' and '#import' statements that does not +# contain extensions when sorting is enabled. +mod_sort_incl_import_prioritize_extensionless = false # true/false + +# Whether to prioritize '#include' and '#import' statements that contain +# angle over quotes when sorting is enabled. +mod_sort_incl_import_prioritize_angle_over_quotes = false # true/false + +# Whether to ignore file extension in '#include' and '#import' statements +# for sorting comparison. +mod_sort_incl_import_ignore_extension = false # true/false + +# Whether to group '#include' and '#import' statements when sorting is enabled. +mod_sort_incl_import_grouping_enabled = false # true/false + +# Whether to move a 'break' that appears after a fully braced 'case' before +# the close brace, as in 'case X: { ... } break;' => 'case X: { ... break; }'. +mod_move_case_break = false # true/false + +# Add or remove braces around a fully braced case statement. Will only remove +# braces if there are no variable declarations in the block. +mod_case_brace = ignore # ignore/add/remove/force + +# Whether to remove a void 'return;' that appears as the last statement in a +# function. +mod_remove_empty_return = true # true/false + +# Add or remove the comma after the last value of an enumeration. +mod_enum_last_comma = add # ignore/add/remove/force + +# (OC) Whether to organize the properties. If true, properties will be +# rearranged according to the mod_sort_oc_property_*_weight factors. +mod_sort_oc_properties = false # true/false + +# (OC) Weight of a class property modifier. +mod_sort_oc_property_class_weight = 0 # number + +# (OC) Weight of 'atomic' and 'nonatomic'. +mod_sort_oc_property_thread_safe_weight = 0 # number + +# (OC) Weight of 'readwrite' when organizing properties. +mod_sort_oc_property_readwrite_weight = 0 # number + +# (OC) Weight of a reference type specifier ('retain', 'copy', 'assign', +# 'weak', 'strong') when organizing properties. +mod_sort_oc_property_reference_weight = 0 # number + +# (OC) Weight of getter type ('getter=') when organizing properties. +mod_sort_oc_property_getter_weight = 0 # number + +# (OC) Weight of setter type ('setter=') when organizing properties. +mod_sort_oc_property_setter_weight = 0 # number + +# (OC) Weight of nullability type ('nullable', 'nonnull', 'null_unspecified', +# 'null_resettable') when organizing properties. +mod_sort_oc_property_nullability_weight = 0 # number + +# +# Preprocessor options +# + +# Add or remove indentation of preprocessor directives inside #if blocks +# at brace level 0 (file-level). +pp_indent = ignore # ignore/add/remove/force + +# Whether to indent #if/#else/#endif at the brace level. If false, these are +# indented from column 1. +pp_indent_at_level = false # true/false + +# Specifies the number of columns to indent preprocessors per level +# at brace level 0 (file-level). If pp_indent_at_level=false, also specifies +# the number of columns to indent preprocessors per level +# at brace level > 0 (function-level). +# +# Default: 1 +pp_indent_count = 1 # unsigned number + +# Add or remove space after # based on pp_level of #if blocks. +pp_space = ignore # ignore/add/remove/force + +# Sets the number of spaces per level added with pp_space. +pp_space_count = 0 # unsigned number + +# The indent for '#region' and '#endregion' in C# and '#pragma region' in +# C/C++. Negative values decrease indent down to the first column. +pp_indent_region = 0 # number + +# Whether to indent the code between #region and #endregion. +pp_region_indent_code = false # true/false + +# If pp_indent_at_level=true, sets the indent for #if, #else and #endif when +# not at file-level. Negative values decrease indent down to the first column. +# +# =0: Indent preprocessors using output_tab_size +# >0: Column at which all preprocessors will be indented +pp_indent_if = 0 # number + +# Whether to indent the code between #if, #else and #endif. +pp_if_indent_code = false # true/false + +# Whether to indent '#define' at the brace level. If false, these are +# indented from column 1. +pp_define_at_level = false # true/false + +# Whether to ignore the '#define' body while formatting. +pp_ignore_define_body = false # true/false + +# Whether to indent case statements between #if, #else, and #endif. +# Only applies to the indent of the preprocesser that the case statements +# directly inside of. +# +# Default: true +pp_indent_case = true # true/false + +# Whether to indent whole function definitions between #if, #else, and #endif. +# Only applies to the indent of the preprocesser that the function definition +# is directly inside of. +# +# Default: true +pp_indent_func_def = true # true/false + +# Whether to indent extern C blocks between #if, #else, and #endif. +# Only applies to the indent of the preprocesser that the extern block is +# directly inside of. +# +# Default: true +pp_indent_extern = true # true/false + +# Whether to indent braces directly inside #if, #else, and #endif. +# Only applies to the indent of the preprocesser that the braces are directly +# inside of. +# +# Default: true +pp_indent_brace = true # true/false + +# +# Sort includes options +# + +# The regex for include category with priority 0. +include_category_0 = "" # string + +# The regex for include category with priority 1. +include_category_1 = "" # string + +# The regex for include category with priority 2. +include_category_2 = "" # string + +# +# Use or Do not Use options +# + +# true: indent_func_call_param will be used (default) +# false: indent_func_call_param will NOT be used +# +# Default: true +use_indent_func_call_param = true # true/false + +# The value of the indentation for a continuation line is calculated +# differently if the statement is: +# - a declaration: your case with QString fileName ... +# - an assignment: your case with pSettings = new QSettings( ... +# +# At the second case the indentation value might be used twice: +# - at the assignment +# - at the function call (if present) +# +# To prevent the double use of the indentation value, use this option with the +# value 'true'. +# +# true: indent_continue will be used only once +# false: indent_continue will be used every time (default) +use_indent_continue_only_once = false # true/false + +# The value might be used twice: +# - at the assignment +# - at the opening brace +# +# To prevent the double use of the indentation value, use this option with the +# value 'true'. +# +# true: indentation will be used only once +# false: indentation will be used every time (default) +indent_cpp_lambda_only_once = false # true/false + +# Whether sp_after_angle takes precedence over sp_inside_fparen. This was the +# historic behavior, but is probably not the desired behavior, so this is off +# by default. +use_sp_after_angle_always = false # true/false + +# Whether to apply special formatting for Qt SIGNAL/SLOT macros. Essentially, +# this tries to format these so that they match Qt's normalized form (i.e. the +# result of QMetaObject::normalizedSignature), which can slightly improve the +# performance of the QObject::connect call, rather than how they would +# otherwise be formatted. +# +# See options_for_QT.cpp for details. +# +# Default: true +use_options_overriding_for_qt_macros = true # true/false + +# If true: the form feed character is removed from the list +# of whitespace characters. +# See https://en.cppreference.com/w/cpp/string/byte/isspace +use_form_feed_no_more_as_whitespace_character = false # true/false + +# +# Warn levels - 1: error, 2: warning (default), 3: note +# + +# (C#) Warning is given if doing tab-to-\t replacement and we have found one +# in a C# verbatim string literal. +# +# Default: 2 +warn_level_tabs_found_in_verbatim_string_literals = 2 # unsigned number + +# Limit the number of loops. +# Used by uncrustify.cpp to exit from infinite loop. +# 0: no limit. +debug_max_number_of_loops = 0 # number + +# Set the number of the line to protocol; +# Used in the function prot_the_line if the 2. parameter is zero. +# 0: nothing protocol. +debug_line_number_to_protocol = 0 # number + +# Set the number of second(s) before terminating formatting the current file, +# 0: no timeout. +# only for linux +debug_timeout = 0 # number + +# Meaning of the settings: +# Ignore - do not do any changes +# Add - makes sure there is 1 or more space/brace/newline/etc +# Force - makes sure there is exactly 1 space/brace/newline/etc, +# behaves like Add in some contexts +# Remove - removes space/brace/newline/etc +# +# +# - Token(s) can be treated as specific type(s) with the 'set' option: +# `set tokenType tokenString [tokenString...]` +# +# Example: +# `set BOOL __AND__ __OR__` +# +# tokenTypes are defined in src/token_enum.h, use them without the +# 'CT_' prefix: 'CT_BOOL' => 'BOOL' +# +# +# - Token(s) can be treated as type(s) with the 'type' option. +# `type tokenString [tokenString...]` +# +# Example: +# `type int c_uint_8 Rectangle` +# +# This can also be achieved with `set TYPE int c_uint_8 Rectangle` +# +# +# To embed whitespace in tokenStrings use the '\' escape character, or quote +# the tokenStrings. These quotes are supported: "'` +# +# +# - Support for the auto detection of languages through the file ending can be +# added using the 'file_ext' command. +# `file_ext langType langString [langString..]` +# +# Example: +# `file_ext CPP .ch .cxx .cpp.in` +# +# langTypes are defined in uncrusify_types.h in the lang_flag_e enum, use +# them without the 'LANG_' prefix: 'LANG_CPP' => 'CPP' +# +# +# - Custom macro-based indentation can be set up using 'macro-open', +# 'macro-else' and 'macro-close'. +# `(macro-open | macro-else | macro-close) tokenString` +# +# Example: +# `macro-open BEGIN_TEMPLATE_MESSAGE_MAP` +# `macro-open BEGIN_MESSAGE_MAP` +# `macro-close END_MESSAGE_MAP` +# +# +# option(s) with 'not default' value: 0 +# diff --git a/win64-tc.cmake b/win64-tc.cmake new file mode 100644 index 0000000..a1084f3 --- /dev/null +++ b/win64-tc.cmake @@ -0,0 +1,29 @@ +if (NOT MXE_HOME) + set(MXE_HOME $ENV{MXE_HOME}) +endif() +if (NOT MXE_HOME) + message(FATAL_ERROR "Please setup MXE_HOME environment variable") +endif() + +if (NOT MXE_CFG) + set(MXE_CFG $ENV{MXE_CFG}) + if (NOT MXE_CFG) + set(MXE_CFG "x86_64-w64-mingw32.static") + endif() +endif() + +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR x86_64) +set(MSYS 1) +set(CMAKE_FIND_ROOT_PATH ${MXE_HOME}/usr/${MXE_CFG}) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_C_COMPILER ${MXE_HOME}/usr/bin/${MXE_CFG}-gcc) +set(CMAKE_CXX_COMPILER ${MXE_HOME}/usr/bin/${MXE_CFG}-g++) +set(CMAKE_RC_COMPILER ${MXE_HOME}/usr/bin/${MXE_CFG}-windres) +set(CMAKE_MODULE_PATH "${MXE_HOME}/src/cmake" ${CMAKE_MODULE_PATH}) # For mxe FindPackage scripts +#set(CMAKE_INSTALL_PREFIX ${MXE_HOME}/usr/x86_64-w64-mingw32.static CACHE PATH "Installation Prefix") +set(CMAKE_CROSS_COMPILING ON) # Workaround for http://www.cmake.org/Bug/view.php?id=14075 +set(CMAKE_RC_COMPILE_OBJECT " -O coff -o ") # Workaround for buggy windres rules +set(PKG_CONFIG_EXECUTABLE ${MXE_HOME}/usr/bin/${MXE_CFG}-pkg-config)