diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml new file mode 100644 index 00000000..781d7328 --- /dev/null +++ b/.github/workflows/release-cli.yml @@ -0,0 +1,49 @@ +on: + release: + types: + - published + +name: Upload C CLI Release Asset + +jobs: + release-c-cli: + name: Upload Release C CLI + if: github.event_name == 'release' && startsWith(github.event.release.name, 'cli-') + strategy: + matrix: + os: [ubuntu-latest] # windows-latest + runs-on: ${{ matrix.os }} + env: + OSNAME: ${{matrix.os == 'ubuntu-latest' && 'linux' || matrix.os == 'windows-latest' && 'windows' || 'macos' }} + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set env var + run: echo "ZIPFILE=sqlitecloud-c-${{ github.event.release.name }}-${{ env.OSNAME }}.zip" >> $GITHUB_ENV + + - name: Build CLI + run: | + cd C + make TLS_STATIC=1 cli + zip ${{ env.ZIPFILE }} sqlitecloud-cli + + - name: Get release + id: release + uses: bruceadams/get-release@v1.2.3 + if: runner.os != 'macOS' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + if: runner.os != 'macOS' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.release.outputs.upload_url }} + asset_path: ./C/${{ env.ZIPFILE }} + asset_name: ${{ env.ZIPFILE }} + asset_content_type: application/zip diff --git a/.gitignore b/.gitignore index fb1d28bb..4fee149e 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ mvdan.cc *.xcexplist *.xcbkptlist native*.go +*.out GO/src/gopkg.in/ini.v1/ GO/src/sqliteweb/dashboard/.nova/Configuration.json @@ -28,25 +29,40 @@ GO/src/sqliteweb/dashboard/.nova/Configuration.json *.plist C/test/test.xcodeproj/xcuserdata/marco.xcuserdatad/xcschemes/xcschememanagement.plist +C/apple/libressl-* +C/apple/build/ +C/apple/fat/ +C/apple/output/ # dependencies JS/node_modules JS/**/node_modules JS/**/**/node_modules +NODEJS/node_modules +NODEJS/**/node_modules +NODEJS/**/**/node_modules +EDGE/node_modules +EDGE/**/node_modules +EDGE/**/**/node_modules + # production JS/public JS/**/public JS/**/**/public - #env -# JS/.env -# JS/**/.env -# JS/**/**/.env +JS/.env +JS/**/.env +JS/**/**/.env #vercel JS/.vercel # JS local example -JS/example/local-sqlite-cloud-chat \ No newline at end of file +JS/example/local-sqlite-cloud-chat + +# NODEJS local example +NODEJS/example +examples/with-typescript-nextjs/.env +C/apple/sqlitecloud-cli diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..4b1a1cac --- /dev/null +++ b/.gitmodules @@ -0,0 +1,12 @@ +[submodule "sqlitecloud-js"] + path = sqlitecloud-js + url = https://github.com/sqlitecloud/sqlitecloud-js +[submodule "sqlitecloud-py"] + path = sqlitecloud-py + url = https://github.com/sqlitecloud/sqlitecloud-py +[submodule "sqlitecloud-php"] + path = sqlitecloud-php + url = https://github.com/sqlitecloud/sqlitecloud-php +[submodule "sqlitecloud-go"] + path = sqlitecloud-go + url = https://github.com/sqlitecloud/sqlitecloud-go diff --git a/C/Makefile b/C/Makefile index c8ce0fc3..36b133cd 100644 --- a/C/Makefile +++ b/C/Makefile @@ -1,7 +1,8 @@ AR := ar CC := gcc -INCLUDES := -I. -Icli -I./SSL/include -OPTIONS := -Wno-macro-redefined -Wno-shift-negative-value -Os +INCLUDES := -I. -Icli +OPTIONS := -Wno-shift-negative-value -Os -fPIC + ifeq ($(OS),Windows_NT) # Windows @@ -10,14 +11,27 @@ else ifeq ($(UNAME_S),Darwin) # MacOS LDFLAGS = -lpthread -ltls - LIBFLAGS = -L./SSL/macos_fat + ifeq ($(TLS_STATIC), 1) + LIBFLAGS = -L./SSL/macos_fat + else + LIBRESSLDIR ?= /opt/homebrew/opt/libressl/lib/ + LIBFLAGS = -L$(LIBRESSLDIR) + endif else # Linux LDFLAGS = -lpthread -ltls - LIBFLAGS = -L./SSL/linux_64bit + ifeq ($(TLS_STATIC), 1) + LIBFLAGS = -L./SSL/linux_64bit + else + LIBRESSLDIR ?= /usr/local/lib/ + # LIBRESSLDIR ?= /usr/local/libressl/lib/ + LIBFLAGS = -L$(LIBRESSLDIR) -Wl,-rpath=$(LIBRESSLDIR) + endif endif endif +all: libsqcloud.a + lz4.o: lz4.c lz4.h $(CC) $(OPTIONS) $(INCLUDES) lz4.c -c -o lz4.o @@ -28,18 +42,18 @@ libsqcloud.a: lz4.o sqcloud.o $(AR) rcs libsqcloud.a *.o libsqcloud.so: lz4.o sqcloud.o - $(CC) -fPIC -shared *.o -o libsqcloud.so + $(CC) -fPIC -shared *.o -o libsqcloud.so ${LIBFLAGS} ${LDFLAGS} libsqcloud.dylib: lz4.o sqcloud.o $(CC) -dynamiclib -install_name -flat_namespace *.o -o libsqcloud.dylib -all: lz4.o +cli: sqlitecloud-cli -cli: libsqcloud.a cli/linenoise.c cli/linenoise.h cli/main.c +sqlitecloud-cli: libsqcloud.a cli/linenoise.c cli/linenoise.h cli/main.c $(CC) $(OPTIONS) $(INCLUDES) cli/*.c libsqcloud.a -o sqlitecloud-cli ${LIBFLAGS} ${LDFLAGS} clean: - rm -rf *.o *.a *.so *.dylib + rm -rf *.o *.a *.so *.dylib sqlitecloud-cli test: cli ./sqlitecloud-cli -h localhost diff --git a/C/README.md b/C/README.md new file mode 100644 index 00000000..2e979cd0 --- /dev/null +++ b/C/README.md @@ -0,0 +1,107 @@ +# C SDK + +SQCloud is the C application programmer's interface to SQLite Cloud. SQCloud is a set of library functions that allow client programs to pass queries and SQL commands to the SQLite Cloud backend server and to receive the results of these queries. In addition to the standard SQLite statements, several other [commands](https://docs.sqlitecloud.io/docs/commands) are supported. + +The following files are required when compiling a C application: + +- sqcloud.c/.h +- lz4.c/.h +- libtls.a (for static linking) or libtls.so/.dylib/.dll (for dynamic linking) + +The header file `sqcloud.h` must be included in your C application. + +All the communications between the client and the server are encrypted and so, you are required to link the LibreSSL (libtls) library with your client. + +### Install LibreSSL + +##### Linux: + +- download the latest portable source from [www.libressl.org](https://www.libressl.org/) + +- extract the tarball and change the directory to the libressl dir + +- compile and install LibreSSL. By default, the install script will install LibreSSL to the `/usr/local/` folder. In order to avoid issue with other SSL libraries installed on the system, you can specify a different install directory, for example `/usr/local/libressl`, with the following command: + + ``` + ./configure --prefix=/usr/local/libressl --with-openssldir=/usr/local/libressl && make && make install + ``` + +##### macOS: + +``` +brew install libressl +``` + + + +### Use the C SDK in your project + +Prerequisites: + +- [Install LibreSSL](#install-libressl) + +Use SQCloud: + +- Include the header file `sqcloud.h` + +- Compile `sqcloud.c ` and `lz4.c`. Examples from the Makefile: + + ``` + lz4.o: lz4.c lz4.h + $(CC) $(OPTIONS) $(INCLUDES) lz4.c -c -o lz4.o + + sqcloud.o: sqcloud.c sqcloud.h + $(CC) $(OPTIONS) $(INCLUDES) sqcloud.c -c -o sqcloud.o + + libsqcloud.a: lz4.o sqcloud.o + $(AR) rcs libsqcloud.a *.o + ``` + +- Link with the compiled sources + +- Link with the libtls shared library using the following gcc option flags: + + - Add the libressl dir to the list of directories to be searched for: -L. Examples: + + linux: `-L/usr/local/libressl/lib/` + + macOS: `-L/opt/homebrew/opt/libressl/lib` + + - make the library available at runtime with the environment variable LD_LIBRARY_PATH or rpath. Example: `-Wl,-rpath=/usr/local/libressl/lib/` + + - link the shared library: `-ltls` + + Example from the Makefile: + + ``` + CC := gcc + INCLUDES := -I. -Icli + OPTIONS := -Wno-macro-redefined -Wno-shift-negative-value -Os + LDFLAGS = -ltls + LIBFLAGS = -L/usr/local/libressl/lib/ -Wl,-rpath=/usr/local/libressl/lib/ + ... + cli: libsqcloud.a cli/linenoise.c cli/linenoise.h cli/main.c + $(CC) $(OPTIONS) $(INCLUDES) cli/*.c libsqcloud.a -o sqlitecloud-cli ${LIBFLAGS} ${LDFLAGS} + ``` + + + +# SQLite Cloud CLI + +The command-line interface is a text-based user interface to interact with a SQLite Cloud server. + +Prerequisites: + +- [Install LibreSSL](#install-libressl) + +Build: + +``` +make cli +``` + +Connect to a SQLite Cloud server: + +``` +./sqlitecloud-cli -h .sqlite.cloud -p 8860 -n admin -m +``` diff --git a/C/apple/Makefile b/C/apple/Makefile new file mode 100644 index 00000000..c4670dbd --- /dev/null +++ b/C/apple/Makefile @@ -0,0 +1,35 @@ +SDKROOT ?= /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk +TARGET ?= macos +LIBTLS = ./fat/libressl/$(TARGET) +LIBTLS_LIB = $(LIBTLS)/lib/libtls.a +LIBTLS_INC = $(LIBTLS)/include +AR := ar +CC ?= /usr/bin/clang -target arm64-apple-darwin -isysroot $(SDKROOT) +INCLUDES = -I../ -I$(LIBTLS_INC) +OPTIONS := -Wno-macro-redefined -Wno-shift-negative-value -O2 -pipe -no-cpp-precomp -Wall -fno-strict-aliasing -fno-strict-overflow -fstack-protector-strong -Qunused-arguments -Wno-pointer-sign +CFLAGS ?= -arch arm64 +LDFLAGS ;= -lpthread +LIBFLAGS ?= + +all: libsqcloud.a + +lz4.o: ../lz4.c ../lz4.h + $(CC) $(OPTIONS) $(CFLAGS) $(INCLUDES) ../lz4.c -c -o lz4.o + +sqcloud.o: ../sqcloud.c ../sqcloud.h + $(CC) $(OPTIONS) $(CFLAGS) $(INCLUDES) ../sqcloud.c -c -o sqcloud.o + +libsqcloud.a: lz4.o sqcloud.o + $(AR) rcs libsqcloud-tmp.a *.o + libtool -static -o libsqcloud.a libsqcloud-tmp.a $(LIBTLS_LIB) + +cli: libsqcloud.a ../cli/linenoise.c ../cli/linenoise.h ../cli/main.c + $(CC) $(OPTIONS) $(CFLAGS) $(INCLUDES) ../cli/*.c libsqcloud.a -o sqlitecloud-cli ${LIBFLAGS} ${LDFLAGS} + +clean: + rm -rf *.o *.a *.so *.dylib sqlitecloud-cli + +buildclean: + rm -rf build Fat output + +.PHONY: all cli clean buildclean diff --git a/C/apple/README.md b/C/apple/README.md new file mode 100644 index 00000000..20061c2d --- /dev/null +++ b/C/apple/README.md @@ -0,0 +1,12 @@ +# libsqcloud XCFramework for iOS, macOS & Catalyst + +A script to compile libsqcloud (C SDK for SQLite Cloud) and its dependencies (libtls) to an XCFramework supporting the latest Apple OS versions + +Instructions: +Build: +``` +bash build-apple.sh +``` + +The resulting XCFrameworks `libsqcloud.xcframework` will be saved in the "output" directory. +The script also compiles the libtls library using the `libressl.sh` script, if needed, and includes it into the libsqcloud.a files. diff --git a/C/apple/build-apple.sh b/C/apple/build-apple.sh new file mode 100755 index 00000000..e2f1e2ef --- /dev/null +++ b/C/apple/build-apple.sh @@ -0,0 +1,584 @@ +#!/bin/bash + +# edit these version numbers to suit your needs, or define them before running the script + +echo "BUILD_TARGETS environment variable can be set as a string split by ':' as you would a PATH variable. Ditto LINK_TARGETS" +# example: +# export BUILD_TARGETS="simulator_x86_64:catalyst_x86_64:macos_x86_64:ios-arm64e" + +IFS=':' read -r -a sqcloud_build_targets <<< "$BUILD_TARGETS" +IFS=':' read -r -a sqcloud_link_targets <<< "$LINK_TARGETS" + +if [ -z "$IOS" ] +then + IOS=`xcrun -sdk iphoneos --show-sdk-version` +fi + +if [ -z "$MIN_IOS_VERSION" ] +then + MIN_IOS_VERSION=13.0 +fi + +if [ -z "$SQCLOUD_VERSION" ] +then + SQCLOUD_VERSION=1.0.0 +fi + +if [ -z "$MACOSX" ] +then + MACOSX=`xcrun --sdk macosx --show-sdk-version|cut -d '.' -f 1-2` +fi + +declare -a all_targets=("ios-arm64" "ios-arm64e" "simulator_x86_64" "simulator_x86_64h" "simulator_arm64e" "simulator_arm64" "catalyst_x86_64" "catalyst_arm64" "macos_x86_64" "macos_x86_64h" "macos_arm64") +declare -a old_targets=("simulator_x86_64" "catalyst_x86_64" "macos_x86_64" "ios-arm64") +declare -a appleSiliconTargets=("simulator_arm64" "simulator_x86_64" "catalyst_x86_64" "catalyst_arm64" "macos_arm64" "macos_x86_64" "ios-arm64") + +if [ -z "$sqcloud_build_targets" ] +then + declare -a sqcloud_build_targets=("simulator_x86_64" "simulator_arm64" "catalyst_x86_64" "catalyst_arm64" "macos_x86_64" "macos_arm64" "ios-arm64") + #declare -a sqcloud_build_targets=("simulator_x86_64" "catalyst_x86_64" "catalyst_arm64" "macos_x86_64" "macos_arm64" "ios-arm64") +fi + +if [ -z "$sqcloud_link_targets" ] +then + declare -a sqcloud_link_targets=("simulator_x86_64" "simulator_arm64" "catalyst_x86_64" "catalyst_arm64" "macos_x86_64" "macos_arm64" "ios-arm64") + #declare -a sqcloud_link_targets=("simulator_x86_64" "catalyst_x86_64" "catalyst_arm64" "macos_x86_64" "macos_arm64" "ios-arm64") +fi + +set -e + +XCODE=`/usr/bin/xcode-select -p` + +# create a staging directory (we need this for include files later on) +#PREFIX=$(pwd)/build/sqcloud-build # this is where we build sqcloud +OUTPUT=$(pwd)/fat/sqcloud # after we build, we put sqcloud outputs here +XCFRAMEWORKS=$(pwd)/output/ # this is where we produce the resulting XCFrameworks: libcrypto.xcframework and libssl.xcframework +LIBTLS_FRAMEWORK=$(pwd)/output/libtls.xcframework + +#mkdir -p $PREFIX +mkdir -p $OUTPUT +mkdir -p $XCFRAMEWORKS + +for target in "${sqcloud_build_targets[@]}" +do + #mkdir -p $PREFIX/$target; + mkdir -p $OUTPUT/$target/lib; + mkdir -p $OUTPUT/$target/bin; + mkdir -p $OUTPUT/$target/include; +done + +# build libtls, if needed +if [ ! -e $LIBTLS_FRAMEWORK ] +then + ./libressl.sh +fi + +# some bash'isms +elementIn () { # source https://stackoverflow.com/questions/3685970/check-if-a-bash-array-contains-a-value + local e match="$1" + shift + for e; do [[ "$e" == "$match" ]] && return 0; done + return 1 +} + +makeSQCloud() { + # only build the files we need (libsqcloud, include files) + CC=$CC CFLAGS=$CFLAGS LIBTLS=$LIBTLS TARGET=$target make clean libsqcloud.a cli +} + +moveSQCloudOutputInPlace() { + local target=$1 + local output=$2 + echo "cp libsqcloud.a $OUTPUT/$target/lib" + cp libsqcloud.a $OUTPUT/$target/lib + cp sqlitecloud-cli $OUTPUT/$target/bin + rsync -am --include='sqcloud.h' --exclude="" -f 'hide,! */' ../*.h $OUTPUT/$target/include +} + +needsRebuilding() { + local target=$1 + test libsqcloud.a -nt Makefile + timestampCompare=$? + if [ $timestampCompare -eq 1 ]; then + return 0 + else + arch=`/usr/bin/lipo -archs libsqcloud.a` + if [ "$arch" == "$target" ]; then + return 1 + else + return 0 + fi + fi +} + +############################################## +## iOS Simulator x86_64h Compilation +############################################## + +target=simulator_x86_64h +if needsRebuilding "$target" && elementIn "$target" "${sqcloud_build_targets[@]}"; then + + + printf "\n\n--> iOS Simulator x86_64h Compilation" + + DEVROOT=$XCODE/Platforms/iPhoneSimulator.platform/Developer + SDKROOT=$DEVROOT/SDKs/iPhoneSimulator${IOS}.sdk + CC="/usr/bin/clang" + CPPFLAGS="-I$SDKROOT/usr/include/" + CFLAGS="$CPPFLAGS -arch x86_64h -miphoneos-version-min=${MIN_IOS_VERSION} -isysroot $SDKROOT" + +# ./configure --host=x86_64-apple-darwin --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang" \ + CPPFLAGS="-I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch x86_64h -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp -isysroot $SDKROOT" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD=$DEVROOT/usr/bin/ld + + makeSQCloud + printf "\n\n--> XX iOS Simulator x86_64h libsqcloud Compilation\n" + moveSQCloudOutputInPlace $target $OUTPUT + +fi; + +############################################# +## iOS Simulator x86_64 libsqcloud Compilation +############################################# + +target=simulator_x86_64 +if needsRebuilding "$target" && elementIn "$target" "${sqcloud_build_targets[@]}"; then + + printf "\n\n--> iOS Simulator x86_64 libsqcloud Compilation\n" + + DEVROOT=$XCODE/Platforms/iPhoneSimulator.platform/Developer + SDKROOT=$DEVROOT/SDKs/iPhoneSimulator${IOS}.sdk + CC="/usr/bin/clang" + CPPFLAGS="-I$SDKROOT/usr/include/" + CFLAGS="$CPPFLAGS -arch x86_64 -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp -isysroot $SDKROOT" + + #echo "prefix: $PREFIX/$target" + echo "SDKROOT: $SDKROOT" + echo "CPPFLAGS: $CPPFLAGS" + echo "IOS: $IOS" + + #./configure --host=x86_64-apple-darwin --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang" \ + CPPFLAGS="-I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch x86_64 -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp -isysroot $SDKROOT" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD=$DEVROOT/usr/bin/ld + + makeSQCloud + echo -printf "\n\n--> XX iOS Simulator x86_64 libsqcloud Compilation\n" + moveSQCloudOutputInPlace $target $OUTPUT + +fi; + +############################################# +## iOS Simulator arm64e libsqcloud Compilation +############################################# + +target=simulator_arm64e +if needsRebuilding "$target" && elementIn "$target" "${sqcloud_build_targets[@]}"; then + + printf "\n\n--> iOS Simulator arm64e libsqcloud Compilation\n" + + DEVROOT=$XCODE/Platforms/iPhoneSimulator.platform/Developer + SDKROOT=$DEVROOT/SDKs/iPhoneSimulator${IOS}.sdk + CC="/usr/bin/clang -target arm64-apple-ios${IOS}-simulator" + CPPFLAGS="-I$SDKROOT/usr/include/ -target arm64-apple-ios${IOS}-simulator" + CFLAGS="$CPPFLAGS -arch arm64e -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp -isysroot $SDKROOT" + + #./configure --build=aarch64-apple-darwin --host=aarch64-apple-darwin22 --prefix="$PREFIX/$target" \ + ./configure --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -target arm64-apple-ios${IOS}-simulator" \ + CPPFLAGS="-I$SDKROOT/usr/include/ -target arm64-apple-ios${IOS}-simulator" \ + CFLAGS="$CPPFLAGS -arch arm64e -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp -isysroot $SDKROOT" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD=$DEVROOT/usr/bin/ld + + makeSQCloud + printf "\n\n--> XX iOS Simulator arm64e libsqcloud Compilation\n" + moveSQCloudOutputInPlace $target $OUTPUT + +fi; + +############################################# +## iOS Simulator arm64 libsqcloud Compilation +############################################# + +target=simulator_arm64 +if needsRebuilding "$target" && elementIn "$target" "${sqcloud_build_targets[@]}"; then + + printf "\n\n--> iOS Simulator arm64 libsqcloud Compilation\n" + + DEVROOT=$XCODE/Platforms/iPhoneSimulator.platform/Developer + SDKROOT=$DEVROOT/SDKs/iPhoneSimulator${IOS}.sdk + CC="/usr/bin/clang -target arm64-apple-ios${IOS}-simulator" + CPPFLAGS="-I$SDKROOT/usr/include/" + CFLAGS="$CPPFLAGS -arch arm64 -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp -isysroot $SDKROOT" + + #./configure --host=aarch64-apple-darwin --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -target arm64-apple-ios${IOS}-simulator" \ + CPPFLAGS="-I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64 -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp -isysroot $SDKROOT" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD=$DEVROOT/usr/bin/ld + + makeSQCloud + printf "\n\n--> XX iOS Simulator arm64 libsqcloud Compilation\n" + moveSQCloudOutputInPlace $target $OUTPUT + +fi; + + +################################## +## iOS arm64 libsqcloud Compilation +################################## + +target=ios-arm64 +if needsRebuilding "$target" && elementIn "$target" "${sqcloud_build_targets[@]}"; then + + printf "\n\n--> iOS arm64 libsqcloud Compilation\n" + + DEVROOT=$XCODE/Platforms/iPhoneOS.platform/Developer + SDKROOT=$DEVROOT/SDKs/iPhoneOS${IOS}.sdk + CC="/usr/bin/clang -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64 -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp" \ + + #./configure --host=aarch64-apple-darwin --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64 -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp -D__arm__=1 $CPPFLAGS" \ + LD=$DEVROOT/usr/bin/ld + + makeSQCloud + printf "\n\n--> XX iOS arm64 libsqcloud Compilation\n" + moveSQCloudOutputInPlace $target $OUTPUT + +fi; + +################################### +## iOS arm64e libsqcloud Compilation +################################### + +target=ios-arm64e +if elementIn "$target" "${sqcloud_build_targets[@]}"; then + + printf "\n\n--> iOS arm64e libsqcloud Compilation\n" + + DEVROOT=$XCODE/Platforms/iPhoneOS.platform/Developer + SDKROOT=$DEVROOT/SDKs/iPhoneOS${IOS}.sdk + CC="/usr/bin/clang -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64e -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp" \ + + #./configure --host=aarch64-apple-darwin19 --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64e -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp -D__arm__=1 $CPPFLAGS" \ + LD=$DEVROOT/usr/bin/ld + + makeSQCloud + printf "\n\n--> XX iOS arm64e libsqcloud Compilation\n" + moveSQCloudOutputInPlace $target $OUTPUT + +fi; + +############################################## +## macOS Catalyst x86_64 libsqcloud Compilation +############################################## + +target=catalyst_x86_64 +if needsRebuilding "$target" && elementIn "$target" "${sqcloud_build_targets[@]}"; then + + printf "\n\n--> macOS Catalyst x86_64 libsqcloud Compilation\n" + + DEVROOT=$XCODE/Platforms/MacOSX.platform/Developer + SDKROOT=$DEVROOT/SDKs/MacOSX${MACOSX}.sdk + CC="/usr/bin/clang -target x86_64-apple-ios${IOS}-macabi -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch x86_64 -pipe -no-cpp-precomp" \ + + #./configure --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -target x86_64-apple-ios${IOS}-macabi -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch x86_64 -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD="/usr/bin/ld" + + makeSQCloud + printf "\n\n--> XX macOS Catalyst x86_64 libsqcloud Compilation\n" + moveSQCloudOutputInPlace $target $OUTPUT + +fi; + +############################################# +## macOS Catalyst arm64 libsqcloud Compilation +############################################# + +target=catalyst_arm64 +if needsRebuilding "$target" && elementIn "$target" "${sqcloud_build_targets[@]}"; then + + printf "\n\n--> macOS Catalyst arm64 libsqcloud Compilation\n" + + DEVROOT=$XCODE/Platforms/MacOSX.platform/Developer + SDKROOT=$DEVROOT/SDKs/MacOSX${MACOSX}.sdk + CC="/usr/bin/clang -target arm64-apple-ios${IOS}-macabi -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64 -pipe -no-cpp-precomp" \ + + #./configure --build=aarch64-apple-darwin --host=aarch64-apple-darwin22 --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -target arm64-apple-ios${IOS}-macabi -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64 -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD="/usr/bin/ld" + + makeSQCloud + printf "\n\n--> XX macOS Catalyst arm64 libsqcloud Compilation\n" + moveSQCloudOutputInPlace $target $OUTPUT + +fi; + + +##################################### +## macOS x86_64 libsqcloud Compilation +##################################### + +target=macos_x86_64 +if needsRebuilding "$target" && elementIn "$target" "${sqcloud_build_targets[@]}"; then + + printf "\n\n--> macOS x86_64 libsqcloud Compilation\n" + + DEVROOT=$XCODE/Platforms/MacOSX.platform/Developer + SDKROOT=$DEVROOT/SDKs/MacOSX${MACOSX}.sdk + CC="/usr/bin/clang -target x86_64-apple-darwin -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch x86_64 -pipe -no-cpp-precomp" \ + + #./configure --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -target x86_64-apple-darwin -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch x86_64 -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD="/usr/bin/clang -target x86_64-apple-darwin" + + makeSQCloud + printf "\n\n--> XX macOS x86_64 libsqcloud Compilation\n" + moveSQCloudOutputInPlace $target $OUTPUT + +fi; + + +###################################### +## macOS x86_64h libsqcloud Compilation +###################################### + +target=macos_x86_64h +if needsRebuilding "$target" && elementIn "$target" "${sqcloud_build_targets[@]}"; then + + printf "\n\n--> macOS x86_64h libsqcloud Compilation\n" + + DEVROOT=$XCODE/Platforms/MacOSX.platform/Developer + SDKROOT=$DEVROOT/SDKs/MacOSX${MACOSX}.sdk + CC="/usr/bin/clang -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch x86_64h -pipe -no-cpp-precomp" \ + + #./configure --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch x86_64h -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD="/usr/bin/ld" + + makeSQCloud + printf "\n\n--> XX macOS x86_64h libsqcloud Compilation\n" + moveSQCloudOutputInPlace $target $OUTPUT + +fi; + +##################################### +## macOS arm64 libsqcloud Compilation +##################################### + +target=macos_arm64 +if needsRebuilding "$target" && elementIn "$target" "${sqcloud_build_targets[@]}"; then + + printf "\n\n--> macOS arm64 libsqcloud Compilation\n" + + DEVROOT=$XCODE/Platforms/MacOSX.platform/Developer + SDKROOT=$DEVROOT/SDKs/MacOSX${MACOSX}.sdk + CC="/usr/bin/clang -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64 -pipe -no-cpp-precomp" \ + + #./configure --host=arm-apple-darwin --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64 -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD="/usr/bin/ld" + + makeSQCloud + printf "\n\n--> XX macOS arm64 libsqcloud Compilation\n" + moveSQCloudOutputInPlace $target $OUTPUT + +fi; + +# TODO: This one isn't working - use "host" to cross compile +##################################### +## macOS arm64e libsqcloud Compilation +##################################### + +target=macos_arm64e +if needsRebuilding "$target" && elementIn "$target" "${sqcloud_build_targets[@]}"; then + + printf "\n\n--> macOS arm64e libsqcloud Compilation\n" + + DEVROOT=$XCODE/Platforms/MacOSX.platform/Developer + SDKROOT=$DEVROOT/SDKs/MacOSX${MACOSX}.sdk + CC="/usr/bin/clang -target arm64-apple-darwin -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64e -pipe -no-cpp-precomp" \ + + #./configure --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -target arm64-apple-darwin -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64e -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD="/usr/bin/ld -target arm64-apple-darwin" + + makeSQCloud + printf "\n\n--> XX macOS arm64e libsqcloud Compilation\n" + moveSQCloudOutputInPlace $target $OUTPUT + +fi; + + +#################################### +## lipo & XCFrameworks for SQCloud +#################################### + +## lipo & XCFramework + +macos=() +catalyst=() +simulator=() +ios=() + + +for target in "${sqcloud_link_targets[@]}" +do + if [[ $target == "ios-"* ]]; then + ios+=($target) + fi + if [[ $target == "simulator_"* ]]; then + simulator+=($target) + fi + if [[ $target == "catalyst_"* ]]; then + catalyst+=($target) + fi + if [[ $target == "macos_"* ]]; then + macos+=($target) + fi +done + +XCFRAMEWORK_LIBSQCLOUD_CMD="xcodebuild -create-xcframework" + +framework_targets=() + +if [ ${#ios[@]} -gt 0 ]; then + lipo_libsqcloud="lipo -create " + + framework_targets+=("ios") + mkdir -p $OUTPUT/ios/lib + mkdir -p $OUTPUT/ios/include + + for target in "${ios[@]}" + do + lipo_libsqcloud="$lipo_libsqcloud $OUTPUT/$target/lib/libsqcloud.a" + rsync -a $OUTPUT/$target/include/* $OUTPUT/ios/include + done + + lipo_libsqcloud="$lipo_libsqcloud -output $OUTPUT/ios/lib/libsqcloud.a" + echo $lipo_libsqcloud + eval $lipo_libsqcloud + + + XCFRAMEWORK_LIBSQCLOUD_CMD="$XCFRAMEWORK_LIBSQCLOUD_CMD -library $OUTPUT/ios/lib/libsqcloud.a" + XCFRAMEWORK_LIBSQCLOUD_CMD="$XCFRAMEWORK_LIBSQCLOUD_CMD -headers $OUTPUT/ios/include" +fi + +if [ ${#catalyst[@]} -gt 0 ]; then + lipo_libsqcloud="lipo -create " + + framework_targets+=("catalyst") + mkdir -p $OUTPUT/catalyst/lib + mkdir -p $OUTPUT/catalyst/include + + for target in "${catalyst[@]}" + do + lipo_libsqcloud="$lipo_libsqcloud $OUTPUT/$target/lib/libsqcloud.a" + rsync -a $OUTPUT/$target/include/* $OUTPUT/catalyst/include + done + + lipo_libsqcloud="$lipo_libsqcloud -output $OUTPUT/catalyst/lib/libsqcloud.a" + echo $lipo_libsqcloud + eval $lipo_libsqcloud + + XCFRAMEWORK_LIBSQCLOUD_CMD="$XCFRAMEWORK_LIBSQCLOUD_CMD -library $OUTPUT/catalyst/lib/libsqcloud.a" + XCFRAMEWORK_LIBSQCLOUD_CMD="$XCFRAMEWORK_LIBSQCLOUD_CMD -headers $OUTPUT/catalyst/include" +fi + +if [ ${#macos[@]} -gt 0 ]; then + lipo_libsqcloud="lipo -create " + + framework_targets+=("macos") + mkdir -p $OUTPUT/macos/lib + mkdir -p $OUTPUT/macos/include + + for target in "${macos[@]}" + do + lipo_libsqcloud="$lipo_libsqcloud $OUTPUT/$target/lib/libsqcloud.a" + rsync -a $OUTPUT/$target/include/* $OUTPUT/macos/include + done + + lipo_libsqcloud="$lipo_libsqcloud -output $OUTPUT/macos/lib/libsqcloud.a" + echo $lipo_libsqcloud + eval $lipo_libsqcloud + + XCFRAMEWORK_LIBSQCLOUD_CMD="$XCFRAMEWORK_LIBSQCLOUD_CMD -library $OUTPUT/macos/lib/libsqcloud.a" + XCFRAMEWORK_LIBSQCLOUD_CMD="$XCFRAMEWORK_LIBSQCLOUD_CMD -headers $OUTPUT/macos/include" +fi + +if [ ${#simulator[@]} -gt 0 ]; then + lipo_libsqcloud="lipo -create " + + framework_targets+=("simulator") + mkdir -p $OUTPUT/simulator/lib + mkdir -p $OUTPUT/simulator/include + + for target in "${simulator[@]}" + do + lipo_libsqcloud="$lipo_libsqcloud $OUTPUT/$target/lib/libsqcloud.a" + rsync -a $OUTPUT/$target/include/* $OUTPUT/simulator/include + done + + lipo_libsqcloud="$lipo_libsqcloud -output $OUTPUT/simulator/lib/libsqcloud.a" + echo $lipo_libsqcloud + eval $lipo_libsqcloud + + XCFRAMEWORK_LIBSQCLOUD_CMD="$XCFRAMEWORK_LIBSQCLOUD_CMD -library $OUTPUT/simulator/lib/libsqcloud.a" + XCFRAMEWORK_LIBSQCLOUD_CMD="$XCFRAMEWORK_LIBSQCLOUD_CMD -headers $OUTPUT/simulator/include" +fi + +XCFRAMEWORK_LIBSQCLOUD_CMD="$XCFRAMEWORK_LIBSQCLOUD_CMD -output $XCFRAMEWORKS/libsqcloud.xcframework" + +echo $XCFRAMEWORK_LIBSQCLOUD_CMD +eval $XCFRAMEWORK_LIBSQCLOUD_CMD + +cd .. diff --git a/C/apple/libressl.sh b/C/apple/libressl.sh new file mode 100755 index 00000000..7ddcf46c --- /dev/null +++ b/C/apple/libressl.sh @@ -0,0 +1,640 @@ +#!/bin/bash + +# edit these version numbers to suit your needs, or define them before running the script + +echo "BUILD_TARGETS environment variable can be set as a string split by ':' as you would a PATH variable. Ditto LINK_TARGETS" +# example: +# export BUILD_TARGETS="simulator_x86_64:catalyst_x86_64:macos_x86_64:ios-arm64e" + +IFS=':' read -r -a libressl_build_targets <<< "$BUILD_TARGETS" +IFS=':' read -r -a libressl_link_targets <<< "$LINK_TARGETS" + +if [ -z "$IOS" ] +then + IOS=`xcrun -sdk iphoneos --show-sdk-version` +fi + +if [ -z "$MIN_IOS_VERSION" ] +then + MIN_IOS_VERSION=13.0 +fi + +if [ -z "$LIBRESSL" ] +then + LIBRESSL=3.7.3 +fi + +if [ -z "$MACOSX" ] +then + MACOSX=`xcrun --sdk macosx --show-sdk-version|cut -d '.' -f 1-2` +fi + +declare -a all_targets=("ios-arm64" "ios-arm64e" "simulator_x86_64" "simulator_x86_64h" "simulator_arm64e" "simulator_arm64" "catalyst_x86_64" "catalyst_arm64" "macos_x86_64" "macos_x86_64h" "macos_arm64") +declare -a old_targets=("simulator_x86_64" "catalyst_x86_64" "macos_x86_64" "ios-arm64") +declare -a appleSiliconTargets=("simulator_arm64" "simulator_x86_64" "catalyst_x86_64" "catalyst_arm64" "macos_arm64" "macos_x86_64" "ios-arm64") + +if [ -z "$libressl_build_targets" ] +then + declare -a libressl_build_targets=("simulator_x86_64" "simulator_arm64" "catalyst_x86_64" "catalyst_arm64" "macos_x86_64" "macos_arm64" "ios-arm64") + #declare -a libressl_build_targets=("simulator_x86_64" "catalyst_x86_64" "catalyst_arm64" "macos_x86_64" "macos_arm64" "ios-arm64") +fi + +if [ -z "$libressl_link_targets" ] +then + declare -a libressl_link_targets=("simulator_x86_64" "simulator_arm64" "catalyst_x86_64" "catalyst_arm64" "macos_x86_64" "macos_arm64" "ios-arm64") + #declare -a libressl_link_targets=("simulator_x86_64" "catalyst_x86_64" "catalyst_arm64" "macos_x86_64" "macos_arm64" "ios-arm64") +fi + +set -e + +XCODE=`/usr/bin/xcode-select -p` + +# download LibreSSL +if [ ! -e "libressl-$LIBRESSL.tar.gz" ] +then + curl -OL "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL}.tar.gz" + tar -zxf "libressl-${LIBRESSL}.tar.gz" +fi + +# create a staging directory (we need this for include files later on) +PREFIX=$(pwd)/build/libressl-build # this is where we build libressl +OUTPUT=$(pwd)/fat/libressl # after we build, we put libressls outputs here +XCFRAMEWORKS=$(pwd)/output/ # this is where we produce the resulting XCFrameworks: libcrypto.xcframework and libssl.xcframework + +mkdir -p $PREFIX +mkdir -p $OUTPUT +mkdir -p $XCFRAMEWORKS + +for target in "${libressl_build_targets[@]}" +do + mkdir -p $PREFIX/$target; + mkdir -p $OUTPUT/$target/lib; + mkdir -p $OUTPUT/$target/include; +done + +cd libressl-${LIBRESSL} + +# this cleans everything out of the build directory so we can have a clean build +if [ -e "./Makefile" ] +then + # since we clean before we build, do we still need this?? + make distclean +fi + +# some bash'isms +elementIn () { # source https://stackoverflow.com/questions/3685970/check-if-a-bash-array-contains-a-value + local e match="$1" + shift + for e; do [[ "$e" == "$match" ]] && return 0; done + return 1 +} + +makeLibreSSL() { + # only build the files we need (libcrypto, libssl, include files) + make -C crypto clean all install + make -C ssl clean all install + make -C tls clean all install + make -C include install +} + +moveLibreSSLOutputInPlace() { + local target=$1 + local output=$2 + cp crypto/.libs/libcrypto.a $OUTPUT/$target/lib + cp ssl/.libs/libssl.a $OUTPUT/$target/lib + cp tls/.libs/libtls.a $OUTPUT/$target/lib + rsync -am --include='*.h' -f 'hide,! */' include/* $OUTPUT/$target/include +} + +needsRebuilding() { + local target=$1 + test tls/.libs/libtls.a -nt Makefile + timestampCompare=$? + if [ $timestampCompare -eq 1 ]; then + return 0 + else + arch=`/usr/bin/lipo -archs tls/.libs/libtls.a` + if [ "$arch" == "$target" ]; then + return 1 + else + return 0 + fi + fi +} + +############################################## +## iOS Simulator x86_64h libssl Compilation +############################################## + +target=simulator_x86_64h +if needsRebuilding "$target" && elementIn "$target" "${libressl_build_targets[@]}"; then + + + printf "\n\n--> iOS Simulator x86_64h libssl Compilation" + + DEVROOT=$XCODE/Platforms/iPhoneSimulator.platform/Developer + SDKROOT=$DEVROOT/SDKs/iPhoneSimulator${IOS}.sdk + + ./configure --host=x86_64-apple-darwin --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang" \ + CPPFLAGS="-I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch x86_64h -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp -isysroot $SDKROOT" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD=$DEVROOT/usr/bin/ld + + makeLibreSSL + printf "\n\n--> XX iOS Simulator x86_64h libssl Compilation" + moveLibreSSLOutputInPlace $target $OUTPUT + +fi; + +############################################# +## iOS Simulator x86_64 libssl Compilation +############################################# + +target=simulator_x86_64 +if needsRebuilding "$target" && elementIn "$target" "${libressl_build_targets[@]}"; then + + printf "\n\n--> iOS Simulator x86_64 libssl Compilation" + + DEVROOT=$XCODE/Platforms/iPhoneSimulator.platform/Developer + SDKROOT=$DEVROOT/SDKs/iPhoneSimulator${IOS}.sdk + + echo "prefix: $PREFIX/$target" + echo "SDKROOT: $SDKROOT" + echo "CPPFLAGS: $CPPFLAGS" + echo "IOS: $IOS" + + ./configure --host=x86_64-apple-darwin --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang" \ + CPPFLAGS="-I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch x86_64 -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp -isysroot $SDKROOT" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD=$DEVROOT/usr/bin/ld + + makeLibreSSL + echo -printf "\n\n--> XX iOS Simulator x86_64 libssl Compilation" + moveLibreSSLOutputInPlace $target $OUTPUT + +fi; + +############################################# +## iOS Simulator arm64e libssl Compilation +############################################# + +target=simulator_arm64e +if needsRebuilding "$target" && elementIn "$target" "${libressl_build_targets[@]}"; then + + printf "\n\n--> iOS Simulator arm64e libssl Compilation" + + DEVROOT=$XCODE/Platforms/iPhoneSimulator.platform/Developer + SDKROOT=$DEVROOT/SDKs/iPhoneSimulator${IOS}.sdk + + #./configure --build=aarch64-apple-darwin --host=aarch64-apple-darwin22 --prefix="$PREFIX/$target" \ + ./configure --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -target arm64-apple-ios${IOS}-simulator" \ + CPPFLAGS="-I$SDKROOT/usr/include/ -target arm64-apple-ios${IOS}-simulator" \ + CFLAGS="$CPPFLAGS -arch arm64e -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp -isysroot $SDKROOT" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD=$DEVROOT/usr/bin/ld + + makeLibreSSL + printf "\n\n--> XX iOS Simulator arm64e libssl Compilation" + moveLibreSSLOutputInPlace $target $OUTPUT + +fi; + +############################################# +## iOS Simulator arm64 libssl Compilation +############################################# + +target=simulator_arm64 +if needsRebuilding "$target" && elementIn "$target" "${libressl_build_targets[@]}"; then + + printf "\n\n--> iOS Simulator arm64 libssl Compilation" + + DEVROOT=$XCODE/Platforms/iPhoneSimulator.platform/Developer + SDKROOT=$DEVROOT/SDKs/iPhoneSimulator${IOS}.sdk + + ./configure --host=aarch64-apple-darwin --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -target arm64-apple-ios${IOS}-simulator" \ + CPPFLAGS="-I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64 -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp -isysroot $SDKROOT" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD=$DEVROOT/usr/bin/ld + + makeLibreSSL + printf "\n\n--> XX iOS Simulator arm64 libssl Compilation" + moveLibreSSLOutputInPlace $target $OUTPUT + +fi; + + +################################## +## iOS arm64 libssl Compilation +################################## + +target=ios-arm64 +if needsRebuilding "$target" && elementIn "$target" "${libressl_build_targets[@]}"; then + + printf "\n\n--> iOS arm64 libssl Compilation" + + DEVROOT=$XCODE/Platforms/iPhoneOS.platform/Developer + SDKROOT=$DEVROOT/SDKs/iPhoneOS${IOS}.sdk + + ./configure --host=aarch64-apple-darwin --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64 -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp -D__arm__=1 $CPPFLAGS" \ + LD=$DEVROOT/usr/bin/ld + + makeLibreSSL + printf "\n\n--> XX iOS arm64 libssl Compilation" + moveLibreSSLOutputInPlace $target $OUTPUT + +fi; + +################################### +## iOS arm64e libssl Compilation +################################### + +target=ios-arm64e +if elementIn "$target" "${libressl_build_targets[@]}"; then + + printf "\n\n--> iOS arm64e libssl Compilation" + + DEVROOT=$XCODE/Platforms/iPhoneOS.platform/Developer + SDKROOT=$DEVROOT/SDKs/iPhoneOS${IOS}.sdk + + ./configure --host=aarch64-apple-darwin19 --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64e -miphoneos-version-min=${MIN_IOS_VERSION} -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp -D__arm__=1 $CPPFLAGS" \ + LD=$DEVROOT/usr/bin/ld + + makeLibreSSL + printf "\n\n--> XX iOS arm64e libssl Compilation" + moveLibreSSLOutputInPlace $target $OUTPUT + +fi; + +############################################## +## macOS Catalyst x86_64 libssl Compilation +############################################## + +target=catalyst_x86_64 +if needsRebuilding "$target" && elementIn "$target" "${libressl_build_targets[@]}"; then + + printf "\n\n--> macOS Catalyst x86_64 libssl Compilation" + + DEVROOT=$XCODE/Platforms/MacOSX.platform/Developer + SDKROOT=$DEVROOT/SDKs/MacOSX${MACOSX}.sdk + + ./configure --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -target x86_64-apple-ios${IOS}-macabi -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch x86_64 -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD="/usr/bin/ld" + + makeLibreSSL + printf "\n\n--> XX macOS Catalyst x86_64 libssl Compilation" + moveLibreSSLOutputInPlace $target $OUTPUT + +fi; + +############################################# +## macOS Catalyst arm64 libssl Compilation +############################################# + +target=catalyst_arm64 +if needsRebuilding "$target" && elementIn "$target" "${libressl_build_targets[@]}"; then + + printf "\n\n--> macOS Catalyst arm64 libssl Compilation" + + DEVROOT=$XCODE/Platforms/MacOSX.platform/Developer + SDKROOT=$DEVROOT/SDKs/MacOSX${MACOSX}.sdk + + ./configure --build=aarch64-apple-darwin --host=aarch64-apple-darwin22 --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -target arm64-apple-ios${IOS}-macabi -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64 -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD="/usr/bin/ld" + + makeLibreSSL + printf "\n\n--> XX macOS Catalyst arm64 libssl Compilation" + moveLibreSSLOutputInPlace $target $OUTPUT + +fi; + + +##################################### +## macOS x86_64 libssl Compilation +##################################### + +target=macos_x86_64 +if needsRebuilding "$target" && elementIn "$target" "${libressl_build_targets[@]}"; then + + printf "\n\n--> macOS x86_64 libssl Compilation" + + DEVROOT=$XCODE/Platforms/MacOSX.platform/Developer + SDKROOT=$DEVROOT/SDKs/MacOSX${MACOSX}.sdk + + ./configure --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -target x86_64-apple-darwin -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch x86_64 -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD="/usr/bin/clang -target x86_64-apple-darwin" + + makeLibreSSL + printf "\n\n--> XX macOS x86_64 libssl Compilation" + moveLibreSSLOutputInPlace $target $OUTPUT + +fi; + + +###################################### +## macOS x86_64h libssl Compilation +###################################### + +target=macos_x86_64h +if needsRebuilding "$target" && elementIn "$target" "${libressl_build_targets[@]}"; then + + printf "\n\n--> macOS x86_64h libssl Compilation" + + DEVROOT=$XCODE/Platforms/MacOSX.platform/Developer + SDKROOT=$DEVROOT/SDKs/MacOSX${MACOSX}.sdk + + ./configure --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch x86_64h -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD="/usr/bin/ld" + + makeLibreSSL + printf "\n\n--> XX macOS x86_64h libssl Compilation" + moveLibreSSLOutputInPlace $target $OUTPUT + +fi; + +##################################### +## macOS arm64 libssl Compilation +##################################### + +target=macos_arm64 +if needsRebuilding "$target" && elementIn "$target" "${libressl_build_targets[@]}"; then + + printf "\n\n--> macOS arm64 libssl Compilation" + + DEVROOT=$XCODE/Platforms/MacOSX.platform/Developer + SDKROOT=$DEVROOT/SDKs/MacOSX${MACOSX}.sdk + + ./configure --host=arm-apple-darwin --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64 -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD="/usr/bin/ld" + + makeLibreSSL + printf "\n\n--> XX macOS arm64 libssl Compilation" + moveLibreSSLOutputInPlace $target $OUTPUT + +fi; + +# TODO: This one isn't working - use "host" to cross compile +##################################### +## macOS arm64e libssl Compilation +##################################### + +target=macos_arm64e +if needsRebuilding "$target" && elementIn "$target" "${libressl_build_targets[@]}"; then + + printf "\n\n--> macOS arm64e libssl Compilation" + + DEVROOT=$XCODE/Platforms/MacOSX.platform/Developer + SDKROOT=$DEVROOT/SDKs/MacOSX${MACOSX}.sdk + + ./configure --prefix="$PREFIX/$target" \ + CC="/usr/bin/clang -target arm64-apple-darwin -isysroot $SDKROOT" \ + CPPFLAGS="-fembed-bitcode -I$SDKROOT/usr/include/" \ + CFLAGS="$CPPFLAGS -arch arm64e -pipe -no-cpp-precomp" \ + CPP="/usr/bin/cpp $CPPFLAGS" \ + LD="/usr/bin/ld -target arm64-apple-darwin" + + makeLibreSSL + printf "\n\n--> XX macOS arm64e libssl Compilation" + moveLibreSSLOutputInPlace $target $OUTPUT + +fi; + + +#################################### +## lipo & XCFrameworks for LibreSSL +#################################### + +## lipo & XCFramework + +macos=() +catalyst=() +simulator=() +ios=() + + +for target in "${libressl_link_targets[@]}" +do + if [[ $target == "ios-"* ]]; then + ios+=($target) + fi + if [[ $target == "simulator_"* ]]; then + simulator+=($target) + fi + if [[ $target == "catalyst_"* ]]; then + catalyst+=($target) + fi + if [[ $target == "macos_"* ]]; then + macos+=($target) + fi +done + +XCFRAMEWORK_LIBSSL_CMD="xcodebuild -create-xcframework" +XCFRAMEWORK_LIBCRYPTO_CMD="xcodebuild -create-xcframework" +XCFRAMEWORK_LIBTLS_CMD="xcodebuild -create-xcframework" + +framework_targets=() + +if [ ${#ios[@]} -gt 0 ]; then + lipo_libssl="lipo -create " + lipo_libcrypto="lipo -create " + lipo_libtls="lipo -create " + + framework_targets+=("ios") + mkdir -p $OUTPUT/ios/lib + mkdir -p $OUTPUT/ios/include + + for target in "${ios[@]}" + do + lipo_libssl="$lipo_libssl $OUTPUT/$target/lib/libssl.a" + lipo_libcrypto="$lipo_libcrypto $OUTPUT/$target/lib/libcrypto.a" + lipo_libtls="$lipo_libtls $OUTPUT/$target/lib/libtls.a" + rsync -a $OUTPUT/$target/include/* $OUTPUT/ios/include + done + + lipo_libssl="$lipo_libssl -output $OUTPUT/ios/lib/libssl.a" + echo $lipo_libssl + eval $lipo_libssl + + lipo_libcrypto="$lipo_libcrypto -output $OUTPUT/ios/lib/libcrypto.a" + echo $lipo_libcrypto + eval $lipo_libcrypto + + lipo_libtls="$lipo_libtls -output $OUTPUT/ios/lib/libtls.a" + echo $lipo_libtls + eval $lipo_libtls + + XCFRAMEWORK_LIBSSL_CMD="$XCFRAMEWORK_LIBSSL_CMD -library $OUTPUT/ios/lib/libssl.a" + XCFRAMEWORK_LIBSSL_CMD="$XCFRAMEWORK_LIBSSL_CMD -headers $OUTPUT/ios/include" + + XCFRAMEWORK_LIBCRYPTO_CMD="$XCFRAMEWORK_LIBCRYPTO_CMD -library $OUTPUT/ios/lib/libcrypto.a" + XCFRAMEWORK_LIBCRYPTO_CMD="$XCFRAMEWORK_LIBCRYPTO_CMD -headers $OUTPUT/ios/include" + + XCFRAMEWORK_LIBTLS_CMD="$XCFRAMEWORK_LIBTLS_CMD -library $OUTPUT/ios/lib/libtls.a" + XCFRAMEWORK_LIBTLS_CMD="$XCFRAMEWORK_LIBTLS_CMD -headers $OUTPUT/ios/include" + +fi + +if [ ${#catalyst[@]} -gt 0 ]; then + lipo_libssl="lipo -create " + lipo_libcrypto="lipo -create " + lipo_libtls="lipo -create " + + framework_targets+=("catalyst") + mkdir -p $OUTPUT/catalyst/lib + mkdir -p $OUTPUT/catalyst/include + + for target in "${catalyst[@]}" + do + lipo_libssl="$lipo_libssl $OUTPUT/$target/lib/libssl.a" + lipo_libcrypto="$lipo_libcrypto $OUTPUT/$target/lib/libcrypto.a" + lipo_libtls="$lipo_libtls $OUTPUT/$target/lib/libtls.a" + rsync -a $OUTPUT/$target/include/* $OUTPUT/catalyst/include + done + + lipo_libssl="$lipo_libssl -output $OUTPUT/catalyst/lib/libssl.a" + echo $lipo_libssl + eval $lipo_libssl + + lipo_libcrypto="$lipo_libcrypto -output $OUTPUT/catalyst/lib/libcrypto.a" + echo $lipo_libcrypto + eval $lipo_libcrypto + + lipo_libtls="$lipo_libtls -output $OUTPUT/catalyst/lib/libtls.a" + echo $lipo_libtls + eval $lipo_libtls + + XCFRAMEWORK_LIBSSL_CMD="$XCFRAMEWORK_LIBSSL_CMD -library $OUTPUT/catalyst/lib/libssl.a" + XCFRAMEWORK_LIBSSL_CMD="$XCFRAMEWORK_LIBSSL_CMD -headers $OUTPUT/catalyst/include" + + XCFRAMEWORK_LIBCRYPTO_CMD="$XCFRAMEWORK_LIBCRYPTO_CMD -library $OUTPUT/catalyst/lib/libcrypto.a" + XCFRAMEWORK_LIBCRYPTO_CMD="$XCFRAMEWORK_LIBCRYPTO_CMD -headers $OUTPUT/catalyst/include" + + XCFRAMEWORK_LIBTLS_CMD="$XCFRAMEWORK_LIBTLS_CMD -library $OUTPUT/catalyst/lib/libtls.a" + XCFRAMEWORK_LIBTLS_CMD="$XCFRAMEWORK_LIBTLS_CMD -headers $OUTPUT/catalyst/include" +fi + +if [ ${#macos[@]} -gt 0 ]; then + lipo_libssl="lipo -create " + lipo_libcrypto="lipo -create " + lipo_libtls="lipo -create " + + framework_targets+=("macos") + mkdir -p $OUTPUT/macos/lib + mkdir -p $OUTPUT/macos/include + + for target in "${macos[@]}" + do + lipo_libssl="$lipo_libssl $OUTPUT/$target/lib/libssl.a" + lipo_libcrypto="$lipo_libcrypto $OUTPUT/$target/lib/libcrypto.a" + lipo_libtls="$lipo_libtls $OUTPUT/$target/lib/libtls.a" + rsync -a $OUTPUT/$target/include/* $OUTPUT/macos/include + done + + lipo_libssl="$lipo_libssl -output $OUTPUT/macos/lib/libssl.a" + echo $lipo_libssl + eval $lipo_libssl + + lipo_libcrypto="$lipo_libcrypto -output $OUTPUT/macos/lib/libcrypto.a" + echo $lipo_libcrypto + eval $lipo_libcrypto + + lipo_libtls="$lipo_libtls -output $OUTPUT/macos/lib/libtls.a" + echo $lipo_libtls + eval $lipo_libtls + + XCFRAMEWORK_LIBSSL_CMD="$XCFRAMEWORK_LIBSSL_CMD -library $OUTPUT/macos/lib/libssl.a" + XCFRAMEWORK_LIBSSL_CMD="$XCFRAMEWORK_LIBSSL_CMD -headers $OUTPUT/macos/include" + + XCFRAMEWORK_LIBCRYPTO_CMD="$XCFRAMEWORK_LIBCRYPTO_CMD -library $OUTPUT/macos/lib/libcrypto.a" + XCFRAMEWORK_LIBCRYPTO_CMD="$XCFRAMEWORK_LIBCRYPTO_CMD -headers $OUTPUT/macos/include" + + XCFRAMEWORK_LIBTLS_CMD="$XCFRAMEWORK_LIBTLS_CMD -library $OUTPUT/macos/lib/libtls.a" + XCFRAMEWORK_LIBTLS_CMD="$XCFRAMEWORK_LIBTLS_CMD -headers $OUTPUT/macos/include" +fi + +if [ ${#simulator[@]} -gt 0 ]; then + lipo_libssl="lipo -create " + lipo_libcrypto="lipo -create " + lipo_libtls="lipo -create " + + framework_targets+=("simulator") + mkdir -p $OUTPUT/simulator/lib + mkdir -p $OUTPUT/simulator/include + + for target in "${simulator[@]}" + do + lipo_libssl="$lipo_libssl $OUTPUT/$target/lib/libssl.a" + lipo_libcrypto="$lipo_libcrypto $OUTPUT/$target/lib/libcrypto.a" + lipo_libtls="$lipo_libtls $OUTPUT/$target/lib/libtls.a" + rsync -a $OUTPUT/$target/include/* $OUTPUT/simulator/include + done + + lipo_libssl="$lipo_libssl -output $OUTPUT/simulator/lib/libssl.a" + echo $lipo_libssl + eval $lipo_libssl + + lipo_libcrypto="$lipo_libcrypto -output $OUTPUT/simulator/lib/libcrypto.a" + echo $lipo_libcrypto + eval $lipo_libcrypto + + lipo_libtls="$lipo_libtls -output $OUTPUT/simulator/lib/libtls.a" + echo $lipo_libtls + eval $lipo_libtls + + + XCFRAMEWORK_LIBSSL_CMD="$XCFRAMEWORK_LIBSSL_CMD -library $OUTPUT/simulator/lib/libssl.a" + XCFRAMEWORK_LIBSSL_CMD="$XCFRAMEWORK_LIBSSL_CMD -headers $OUTPUT/simulator/include" + + XCFRAMEWORK_LIBCRYPTO_CMD="$XCFRAMEWORK_LIBCRYPTO_CMD -library $OUTPUT/simulator/lib/libcrypto.a" + XCFRAMEWORK_LIBCRYPTO_CMD="$XCFRAMEWORK_LIBCRYPTO_CMD -headers $OUTPUT/simulator/include" + + XCFRAMEWORK_LIBTLS_CMD="$XCFRAMEWORK_LIBTLS_CMD -library $OUTPUT/simulator/lib/libtls.a" + XCFRAMEWORK_LIBTLS_CMD="$XCFRAMEWORK_LIBTLS_CMD -headers $OUTPUT/simulator/include" +fi + +XCFRAMEWORK_LIBSSL_CMD="$XCFRAMEWORK_LIBSSL_CMD -output $XCFRAMEWORKS/libssl.xcframework" +XCFRAMEWORK_LIBCRYPTO_CMD="$XCFRAMEWORK_LIBCRYPTO_CMD -output $XCFRAMEWORKS/libcrypto.xcframework" +XCFRAMEWORK_LIBTLS_CMD="$XCFRAMEWORK_LIBTLS_CMD -output $XCFRAMEWORKS/libtls.xcframework" + +#echo $XCFRAMEWORK_LIBSSL_CMD +#eval $XCFRAMEWORK_LIBSSL_CMD +#echo $XCFRAMEWORK_LIBCRYPTO_CMD +#eval $XCFRAMEWORK_LIBCRYPTO_CMD +echo $XCFRAMEWORK_LIBTLS_CMD +eval $XCFRAMEWORK_LIBTLS_CMD + +cd .. diff --git a/C/cli/linenoise.c b/C/cli/linenoise.c index cfe51e76..5e8aee57 100644 --- a/C/cli/linenoise.c +++ b/C/cli/linenoise.c @@ -10,7 +10,7 @@ * * ------------------------------------------------------------------------ * - * Copyright (c) 2010-2016, Salvatore Sanfilippo + * Copyright (c) 2010-2023, Salvatore Sanfilippo * Copyright (c) 2010-2013, Pieter Noordhuis * * All rights reserved. @@ -123,6 +123,9 @@ static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; static linenoiseCompletionCallback *completionCallback = NULL; static linenoiseHintsCallback *hintsCallback = NULL; static linenoiseFreeHintsCallback *freeHintsCallback = NULL; +static char *linenoiseNoTTY(void); +static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags); +static void refreshLineWithFlags(struct linenoiseState *l, int flags); static struct termios orig_termios; /* In order to restore at exit.*/ static int maskmode = 0; /* Show "***" instead of input. For passwords. */ @@ -133,24 +136,6 @@ static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; static int history_len = 0; static char **history = NULL; -/* The linenoiseState structure represents the state during line editing. - * We pass this state to functions implementing specific editing - * functionalities. */ -struct linenoiseState { - int ifd; /* Terminal stdin file descriptor. */ - int ofd; /* Terminal stdout file descriptor. */ - char *buf; /* Edited line buffer. */ - size_t buflen; /* Edited line buffer size. */ - const char *prompt; /* Prompt to display. */ - size_t plen; /* Prompt length. */ - size_t pos; /* Current cursor position. */ - size_t oldpos; /* Previous refresh cursor position. */ - size_t len; /* Current edited line length. */ - size_t cols; /* Number of columns in terminal. */ - size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ - int history_index; /* The history index we are currently editing. */ -}; - enum KEY_ACTION{ KEY_NULL = 0, /* NULL */ CTRL_A = 1, /* Ctrl+a */ @@ -175,6 +160,9 @@ enum KEY_ACTION{ static void linenoiseAtExit(void); int linenoiseHistoryAdd(const char *line); +#define REFRESH_CLEAN (1<<0) // Clean the old prompt from the screen +#define REFRESH_WRITE (1<<1) // Rewrite the prompt on the screen. +#define REFRESH_ALL (REFRESH_CLEAN|REFRESH_WRITE) // Do both. static void refreshLine(struct linenoiseState *l); /* Debugging macro. */ @@ -187,7 +175,7 @@ FILE *lndebug_fp = NULL; fprintf(lndebug_fp, \ "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ - (int)l->maxrows,old_rows); \ + (int)l->oldrows,old_rows); \ } \ fprintf(lndebug_fp, ", " __VA_ARGS__); \ fflush(lndebug_fp); \ @@ -355,63 +343,94 @@ static void freeCompletions(linenoiseCompletions *lc) { free(lc->cvec); } -/* This is an helper function for linenoiseEdit() and is called when the +/* Called by completeLine() and linenoiseShow() to render the current + * edited line with the proposed completion. If the current completion table + * is already available, it is passed as second argument, otherwise the + * function will use the callback to obtain it. + * + * Flags are the same as refreshLine*(), that is REFRESH_* macros. */ +static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags) { + /* Obtain the table of completions if the caller didn't provide one. */ + linenoiseCompletions ctable = { 0, NULL }; + if (lc == NULL) { + completionCallback(ls->buf,&ctable); + lc = &ctable; + } + + /* Show the edited line with completion if possible, or just refresh. */ + if (ls->completion_idx < lc->len) { + struct linenoiseState saved = *ls; + ls->len = ls->pos = strlen(lc->cvec[ls->completion_idx]); + ls->buf = lc->cvec[ls->completion_idx]; + refreshLineWithFlags(ls,flags); + ls->len = saved.len; + ls->pos = saved.pos; + ls->buf = saved.buf; + } else { + refreshLineWithFlags(ls,flags); + } + + /* Free the completions table if needed. */ + if (lc != &ctable) freeCompletions(&ctable); +} + +/* This is an helper function for linenoiseEdit*() and is called when the * user types the key in order to complete the string currently in the * input. * * The state of the editing is encapsulated into the pointed linenoiseState - * structure as described in the structure definition. */ -static int completeLine(struct linenoiseState *ls) { + * structure as described in the structure definition. + * + * If the function returns non-zero, the caller should handle the + * returned value as a byte read from the standard input, and process + * it as usually: this basically means that the function may return a byte + * read from the termianl but not processed. Otherwise, if zero is returned, + * the input was consumed by the completeLine() function to navigate the + * possible completions, and the caller should read for the next characters + * from stdin. */ +static int completeLine(struct linenoiseState *ls, int keypressed) { linenoiseCompletions lc = { 0, NULL }; - int nread, nwritten; - char c = 0; + int nwritten; + char c = keypressed; completionCallback(ls->buf,&lc); if (lc.len == 0) { linenoiseBeep(); + ls->in_completion = 0; } else { - size_t stop = 0, i = 0; - - while(!stop) { - /* Show completion or original buffer */ - if (i < lc.len) { - struct linenoiseState saved = *ls; - - ls->len = ls->pos = strlen(lc.cvec[i]); - ls->buf = lc.cvec[i]; - refreshLine(ls); - ls->len = saved.len; - ls->pos = saved.pos; - ls->buf = saved.buf; - } else { - refreshLine(ls); - } - - nread = read(ls->ifd,&c,1); - if (nread <= 0) { - freeCompletions(&lc); - return -1; - } + switch(c) { + case 9: /* tab */ + if (ls->in_completion == 0) { + ls->in_completion = 1; + ls->completion_idx = 0; + } else { + ls->completion_idx = (ls->completion_idx+1) % (lc.len+1); + if (ls->completion_idx == lc.len) linenoiseBeep(); + } + c = 0; + break; + case 27: /* escape */ + /* Re-show original buffer */ + if (ls->completion_idx < lc.len) refreshLine(ls); + ls->in_completion = 0; + c = 0; + break; + default: + /* Update buffer and return */ + if (ls->completion_idx < lc.len) { + nwritten = snprintf(ls->buf,ls->buflen,"%s", + lc.cvec[ls->completion_idx]); + ls->len = ls->pos = nwritten; + } + ls->in_completion = 0; + break; + } - switch(c) { - case 9: /* tab */ - i = (i+1) % (lc.len+1); - if (i == lc.len) linenoiseBeep(); - break; - case 27: /* escape */ - /* Re-show original buffer */ - if (i < lc.len) refreshLine(ls); - stop = 1; - break; - default: - /* Update buffer and return */ - if (i < lc.len) { - nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); - ls->len = ls->pos = nwritten; - } - stop = 1; - break; - } + /* Show completion or original buffer */ + if (ls->in_completion && ls->completion_idx < lc.len) { + refreshLineWithCompletion(ls,&lc,REFRESH_ALL); + } else { + refreshLine(ls); } } @@ -514,8 +533,11 @@ void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { /* Single line low level line refresh. * * Rewrite the currently edited line accordingly to the buffer content, - * cursor position, and number of columns of the terminal. */ -static void refreshSingleLine(struct linenoiseState *l) { + * cursor position, and number of columns of the terminal. + * + * Flags is REFRESH_* macros. The function can just remove the old + * prompt, just write it, or both. */ +static void refreshSingleLine(struct linenoiseState *l, int flags) { char seq[64]; size_t plen = strlen(l->prompt); int fd = l->ofd; @@ -535,23 +557,31 @@ static void refreshSingleLine(struct linenoiseState *l) { abInit(&ab); /* Cursor to left edge */ - snprintf(seq,64,"\r"); + snprintf(seq,sizeof(seq),"\r"); abAppend(&ab,seq,strlen(seq)); - /* Write the prompt and the current buffer content */ - abAppend(&ab,l->prompt,strlen(l->prompt)); - if (maskmode == 1) { - while (len--) abAppend(&ab,"*",1); - } else { - abAppend(&ab,buf,len); + + if (flags & REFRESH_WRITE) { + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + if (maskmode == 1) { + while (len--) abAppend(&ab,"*",1); + } else { + abAppend(&ab,buf,len); + } + /* Show hits if any. */ + refreshShowHints(&ab,l,plen); } - /* Show hits if any. */ - refreshShowHints(&ab,l,plen); + /* Erase to right */ - snprintf(seq,64,"\x1b[0K"); - abAppend(&ab,seq,strlen(seq)); - /* Move cursor to original position. */ - snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); + snprintf(seq,sizeof(seq),"\x1b[0K"); abAppend(&ab,seq,strlen(seq)); + + if (flags & REFRESH_WRITE) { + /* Move cursor to original position. */ + snprintf(seq,sizeof(seq),"\r\x1b[%dC", (int)(pos+plen)); + abAppend(&ab,seq,strlen(seq)); + } + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ abFree(&ab); } @@ -559,88 +589,97 @@ static void refreshSingleLine(struct linenoiseState *l) { /* Multi line low level line refresh. * * Rewrite the currently edited line accordingly to the buffer content, - * cursor position, and number of columns of the terminal. */ -static void refreshMultiLine(struct linenoiseState *l) { + * cursor position, and number of columns of the terminal. + * + * Flags is REFRESH_* macros. The function can just remove the old + * prompt, just write it, or both. */ +static void refreshMultiLine(struct linenoiseState *l, int flags) { char seq[64]; int plen = strlen(l->prompt); int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ int rpos2; /* rpos after refresh. */ int col; /* colum position, zero-based. */ - int old_rows = l->maxrows; + int old_rows = l->oldrows; int fd = l->ofd, j; struct abuf ab; - /* Update maxrows if needed. */ - if (rows > (int)l->maxrows) l->maxrows = rows; + l->oldrows = rows; /* First step: clear all the lines used before. To do so start by * going to the last row. */ abInit(&ab); - if (old_rows-rpos > 0) { - lndebug("go down %d", old_rows-rpos); - snprintf(seq,64,"\x1b[%dB", old_rows-rpos); - abAppend(&ab,seq,strlen(seq)); + + if (flags & REFRESH_CLEAN) { + if (old_rows-rpos > 0) { + lndebug("go down %d", old_rows-rpos); + snprintf(seq,64,"\x1b[%dB", old_rows-rpos); + abAppend(&ab,seq,strlen(seq)); + } + + /* Now for every row clear it, go up. */ + for (j = 0; j < old_rows-1; j++) { + lndebug("clear+up"); + snprintf(seq,64,"\r\x1b[0K\x1b[1A"); + abAppend(&ab,seq,strlen(seq)); + } } - /* Now for every row clear it, go up. */ - for (j = 0; j < old_rows-1; j++) { - lndebug("clear+up"); - snprintf(seq,64,"\r\x1b[0K\x1b[1A"); + if (flags & REFRESH_ALL) { + /* Clean the top line. */ + lndebug("clear"); + snprintf(seq,64,"\r\x1b[0K"); abAppend(&ab,seq,strlen(seq)); } - /* Clean the top line. */ - lndebug("clear"); - snprintf(seq,64,"\r\x1b[0K"); - abAppend(&ab,seq,strlen(seq)); + if (flags & REFRESH_WRITE) { + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + if (maskmode == 1) { + unsigned int i; + for (i = 0; i < l->len; i++) abAppend(&ab,"*",1); + } else { + abAppend(&ab,l->buf,l->len); + } - /* Write the prompt and the current buffer content */ - abAppend(&ab,l->prompt,strlen(l->prompt)); - if (maskmode == 1) { - unsigned int i; - for (i = 0; i < l->len; i++) abAppend(&ab,"*",1); - } else { - abAppend(&ab,l->buf,l->len); - } + /* Show hits if any. */ + refreshShowHints(&ab,l,plen); + + /* If we are at the very end of the screen with our prompt, we need to + * emit a newline and move the prompt to the first column. */ + if (l->pos && + l->pos == l->len && + (l->pos+plen) % l->cols == 0) + { + lndebug(""); + abAppend(&ab,"\n",1); + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + rows++; + if (rows > (int)l->oldrows) l->oldrows = rows; + } - /* Show hits if any. */ - refreshShowHints(&ab,l,plen); - - /* If we are at the very end of the screen with our prompt, we need to - * emit a newline and move the prompt to the first column. */ - if (l->pos && - l->pos == l->len && - (l->pos+plen) % l->cols == 0) - { - lndebug(""); - abAppend(&ab,"\n",1); - snprintf(seq,64,"\r"); - abAppend(&ab,seq,strlen(seq)); - rows++; - if (rows > (int)l->maxrows) l->maxrows = rows; - } + /* Move cursor to right position. */ + rpos2 = (plen+l->pos+l->cols)/l->cols; /* Current cursor relative row */ + lndebug("rpos2 %d", rpos2); - /* Move cursor to right position. */ - rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ - lndebug("rpos2 %d", rpos2); + /* Go up till we reach the expected positon. */ + if (rows-rpos2 > 0) { + lndebug("go-up %d", rows-rpos2); + snprintf(seq,64,"\x1b[%dA", rows-rpos2); + abAppend(&ab,seq,strlen(seq)); + } - /* Go up till we reach the expected positon. */ - if (rows-rpos2 > 0) { - lndebug("go-up %d", rows-rpos2); - snprintf(seq,64,"\x1b[%dA", rows-rpos2); + /* Set column. */ + col = (plen+(int)l->pos) % (int)l->cols; + lndebug("set col %d", 1+col); + if (col) + snprintf(seq,64,"\r\x1b[%dC", col); + else + snprintf(seq,64,"\r"); abAppend(&ab,seq,strlen(seq)); } - /* Set column. */ - col = (plen+(int)l->pos) % (int)l->cols; - lndebug("set col %d", 1+col); - if (col) - snprintf(seq,64,"\r\x1b[%dC", col); - else - snprintf(seq,64,"\r"); - abAppend(&ab,seq,strlen(seq)); - lndebug("\n"); l->oldpos = l->pos; @@ -650,11 +689,33 @@ static void refreshMultiLine(struct linenoiseState *l) { /* Calls the two low level functions refreshSingleLine() or * refreshMultiLine() according to the selected mode. */ +static void refreshLineWithFlags(struct linenoiseState *l, int flags) { + if (mlmode) + refreshMultiLine(l,flags); + else + refreshSingleLine(l,flags); +} + +/* Utility function to avoid specifying REFRESH_ALL all the times. */ static void refreshLine(struct linenoiseState *l) { + refreshLineWithFlags(l,REFRESH_ALL); +} + +/* Hide the current line, when using the multiplexing API. */ +void linenoiseHide(struct linenoiseState *l) { if (mlmode) - refreshMultiLine(l); + refreshMultiLine(l,REFRESH_CLEAN); else - refreshSingleLine(l); + refreshSingleLine(l,REFRESH_CLEAN); +} + +/* Show the current line, when using the multiplexing API. */ +void linenoiseShow(struct linenoiseState *l) { + if (l->in_completion) { + refreshLineWithCompletion(l,NULL,REFRESH_WRITE); + } else { + refreshLineWithFlags(l,REFRESH_WRITE); + } } /* Insert the character 'c' at cursor current position. @@ -783,196 +844,276 @@ void linenoiseEditDeletePrevWord(struct linenoiseState *l) { refreshLine(l); } -/* This function is the core of the line editing capability of linenoise. - * It expects 'fd' to be already in "raw mode" so that every key pressed - * will be returned ASAP to read(). +/* This function is part of the multiplexed API of Linenoise, that is used + * in order to implement the blocking variant of the API but can also be + * called by the user directly in an event driven program. It will: * - * The resulting string is put into 'buf' when the user type enter, or - * when ctrl+d is typed. + * 1. Initialize the linenoise state passed by the user. + * 2. Put the terminal in RAW mode. + * 3. Show the prompt. + * 4. Return control to the user, that will have to call linenoiseEditFeed() + * each time there is some data arriving in the standard input. * - * The function returns the length of the current buffer. */ -static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) -{ - struct linenoiseState l; - + * The user can also call linenoiseEditHide() and linenoiseEditShow() if it + * is required to show some input arriving asyncronously, without mixing + * it with the currently edited line. + * + * When linenoiseEditFeed() returns non-NULL, the user finished with the + * line editing session (pressed enter CTRL-D/C): in this case the caller + * needs to call linenoiseEditStop() to put back the terminal in normal + * mode. This will not destroy the buffer, as long as the linenoiseState + * is still valid in the context of the caller. + * + * The function returns 0 on success, or -1 if writing to standard output + * fails. If stdin_fd or stdout_fd are set to -1, the default is to use + * STDIN_FILENO and STDOUT_FILENO. + */ +int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) { /* Populate the linenoise state that we pass to functions implementing * specific editing functionalities. */ - l.ifd = stdin_fd; - l.ofd = stdout_fd; - l.buf = buf; - l.buflen = buflen; - l.prompt = prompt; - l.plen = strlen(prompt); - l.oldpos = l.pos = 0; - l.len = 0; - l.cols = getColumns(stdin_fd, stdout_fd); - l.maxrows = 0; - l.history_index = 0; + l->in_completion = 0; + l->ifd = stdin_fd != -1 ? stdin_fd : STDIN_FILENO; + l->ofd = stdout_fd != -1 ? stdout_fd : STDOUT_FILENO; + l->buf = buf; + l->buflen = buflen; + l->prompt = prompt; + l->plen = strlen(prompt); + l->oldpos = l->pos = 0; + l->len = 0; + l->cols = getColumns(stdin_fd, stdout_fd); + l->oldrows = 0; + l->history_index = 0; /* Buffer starts empty. */ - l.buf[0] = '\0'; - l.buflen--; /* Make sure there is always space for the nulterm */ + l->buf[0] = '\0'; + l->buflen--; /* Make sure there is always space for the nulterm */ + + /* If stdin is not a tty, stop here with the initialization. We + * will actually just read a line from standard input in blocking + * mode later, in linenoiseEditFeed(). */ + if (!isatty(l->ifd)) return 0; + + /* Enter raw mode. */ + if (enableRawMode(l->ifd) == -1) return -1; /* The latest history entry is always our current buffer, that * initially is just an empty string. */ linenoiseHistoryAdd(""); - if (write(l.ofd,prompt,l.plen) == -1) return -1; - while(1) { - char c; - int nread; - char seq[3]; - - nread = read(l.ifd,&c,1); - if (nread <= 0) return l.len; - - /* Only autocomplete when the callback is set. It returns < 0 when - * there was an error reading from fd. Otherwise it will return the - * character that should be handled next. */ - if (c == 9 && completionCallback != NULL) { - c = completeLine(&l); - /* Return on errors */ - if (c < 0) return l.len; - /* Read next character when 0 */ - if (c == 0) continue; - } + if (write(l->ofd,prompt,l->plen) == -1) return -1; + return 0; +} - switch(c) { - case ENTER: /* enter */ +char *linenoiseEditMore = "If you see this, you are misusing the API: when linenoiseEditFeed() is called, if it returns linenoiseEditMore the user is yet editing the line. See the README file for more information."; + +/* This function is part of the multiplexed API of linenoise, see the top + * comment on linenoiseEditStart() for more information. Call this function + * each time there is some data to read from the standard input file + * descriptor. In the case of blocking operations, this function can just be + * called in a loop, and block. + * + * The function returns linenoiseEditMore to signal that line editing is still + * in progress, that is, the user didn't yet pressed enter / CTRL-D. Otherwise + * the function returns the pointer to the heap-allocated buffer with the + * edited line, that the user should free with linenoiseFree(). + * + * On special conditions, NULL is returned and errno is populated: + * + * EAGAIN if the user pressed Ctrl-C + * ENOENT if the user pressed Ctrl-D + * + * Some other errno: I/O error. + */ +char *linenoiseEditFeed(struct linenoiseState *l) { + /* Not a TTY, pass control to line reading without character + * count limits. */ + if (!isatty(l->ifd)) return linenoiseNoTTY(); + + char c; + int nread; + char seq[3]; + + nread = read(l->ifd,&c,1); + if (nread <= 0) return NULL; + + /* Only autocomplete when the callback is set. It returns < 0 when + * there was an error reading from fd. Otherwise it will return the + * character that should be handled next. */ + if ((l->in_completion || c == 9) && completionCallback != NULL) { + c = completeLine(l,c); + /* Return on errors */ + if (c < 0) return NULL; + /* Read next character when 0 */ + if (c == 0) return linenoiseEditMore; + } + + switch(c) { + case ENTER: /* enter */ + history_len--; + free(history[history_len]); + if (mlmode) linenoiseEditMoveEnd(l); + if (hintsCallback) { + /* Force a refresh without hints to leave the previous + * line as the user typed it after a newline. */ + linenoiseHintsCallback *hc = hintsCallback; + hintsCallback = NULL; + refreshLine(l); + hintsCallback = hc; + } + return strdup(l->buf); + case CTRL_C: /* ctrl-c */ + errno = EAGAIN; + return NULL; + case BACKSPACE: /* backspace */ + case 8: /* ctrl-h */ + linenoiseEditBackspace(l); + break; + case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the + line is empty, act as end-of-file. */ + if (l->len > 0) { + linenoiseEditDelete(l); + } else { history_len--; free(history[history_len]); - if (mlmode) linenoiseEditMoveEnd(&l); - if (hintsCallback) { - /* Force a refresh without hints to leave the previous - * line as the user typed it after a newline. */ - linenoiseHintsCallback *hc = hintsCallback; - hintsCallback = NULL; - refreshLine(&l); - hintsCallback = hc; - } - return (int)l.len; - case CTRL_C: /* ctrl-c */ - errno = EAGAIN; - return -1; - case BACKSPACE: /* backspace */ - case 8: /* ctrl-h */ - linenoiseEditBackspace(&l); - break; - case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the - line is empty, act as end-of-file. */ - if (l.len > 0) { - linenoiseEditDelete(&l); - } else { - history_len--; - free(history[history_len]); - return -1; - } - break; - case CTRL_T: /* ctrl-t, swaps current character with previous. */ - if (l.pos > 0 && l.pos < l.len) { - int aux = buf[l.pos-1]; - buf[l.pos-1] = buf[l.pos]; - buf[l.pos] = aux; - if (l.pos != l.len-1) l.pos++; - refreshLine(&l); - } - break; - case CTRL_B: /* ctrl-b */ - linenoiseEditMoveLeft(&l); - break; - case CTRL_F: /* ctrl-f */ - linenoiseEditMoveRight(&l); - break; - case CTRL_P: /* ctrl-p */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); - break; - case CTRL_N: /* ctrl-n */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); - break; - case ESC: /* escape sequence */ - /* Read the next two bytes representing the escape sequence. - * Use two calls to handle slow terminals returning the two - * chars at different times. */ - if (read(l.ifd,seq,1) == -1) break; - if (read(l.ifd,seq+1,1) == -1) break; - - /* ESC [ sequences. */ - if (seq[0] == '[') { - if (seq[1] >= '0' && seq[1] <= '9') { - /* Extended escape, read additional byte. */ - if (read(l.ifd,seq+2,1) == -1) break; - if (seq[2] == '~') { - switch(seq[1]) { - case '3': /* Delete key. */ - linenoiseEditDelete(&l); - break; - } - } - } else { + errno = ENOENT; + return NULL; + } + break; + case CTRL_T: /* ctrl-t, swaps current character with previous. */ + if (l->pos > 0 && l->pos < l->len) { + int aux = l->buf[l->pos-1]; + l->buf[l->pos-1] = l->buf[l->pos]; + l->buf[l->pos] = aux; + if (l->pos != l->len-1) l->pos++; + refreshLine(l); + } + break; + case CTRL_B: /* ctrl-b */ + linenoiseEditMoveLeft(l); + break; + case CTRL_F: /* ctrl-f */ + linenoiseEditMoveRight(l); + break; + case CTRL_P: /* ctrl-p */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_PREV); + break; + case CTRL_N: /* ctrl-n */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_NEXT); + break; + case ESC: /* escape sequence */ + /* Read the next two bytes representing the escape sequence. + * Use two calls to handle slow terminals returning the two + * chars at different times. */ + if (read(l->ifd,seq,1) == -1) break; + if (read(l->ifd,seq+1,1) == -1) break; + + /* ESC [ sequences. */ + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended escape, read additional byte. */ + if (read(l->ifd,seq+2,1) == -1) break; + if (seq[2] == '~') { switch(seq[1]) { - case 'A': /* Up */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); - break; - case 'B': /* Down */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); - break; - case 'C': /* Right */ - linenoiseEditMoveRight(&l); - break; - case 'D': /* Left */ - linenoiseEditMoveLeft(&l); - break; - case 'H': /* Home */ - linenoiseEditMoveHome(&l); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(&l); + case '3': /* Delete key. */ + linenoiseEditDelete(l); break; } } - } - - /* ESC O sequences. */ - else if (seq[0] == 'O') { + } else { switch(seq[1]) { + case 'A': /* Up */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_PREV); + break; + case 'B': /* Down */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_NEXT); + break; + case 'C': /* Right */ + linenoiseEditMoveRight(l); + break; + case 'D': /* Left */ + linenoiseEditMoveLeft(l); + break; case 'H': /* Home */ - linenoiseEditMoveHome(&l); + linenoiseEditMoveHome(l); break; case 'F': /* End*/ - linenoiseEditMoveEnd(&l); + linenoiseEditMoveEnd(l); break; } } - break; - default: - if (linenoiseEditInsert(&l,c)) return -1; - break; - case CTRL_U: /* Ctrl+u, delete the whole line. */ - buf[0] = '\0'; - l.pos = l.len = 0; - refreshLine(&l); - break; - case CTRL_K: /* Ctrl+k, delete from current to end of line. */ - buf[l.pos] = '\0'; - l.len = l.pos; - refreshLine(&l); - break; - case CTRL_A: /* Ctrl+a, go to the start of the line */ - linenoiseEditMoveHome(&l); - break; - case CTRL_E: /* ctrl+e, go to the end of the line */ - linenoiseEditMoveEnd(&l); - break; - case CTRL_L: /* ctrl+l, clear screen */ - linenoiseClearScreen(); - refreshLine(&l); - break; - case CTRL_W: /* ctrl+w, delete previous word */ - linenoiseEditDeletePrevWord(&l); - break; } + + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch(seq[1]) { + case 'H': /* Home */ + linenoiseEditMoveHome(l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(l); + break; + } + } + break; + default: + if (linenoiseEditInsert(l,c)) return NULL; + break; + case CTRL_U: /* Ctrl+u, delete the whole line. */ + l->buf[0] = '\0'; + l->pos = l->len = 0; + refreshLine(l); + break; + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ + l->buf[l->pos] = '\0'; + l->len = l->pos; + refreshLine(l); + break; + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(l); + break; + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(l); + break; + case CTRL_L: /* ctrl+l, clear screen */ + linenoiseClearScreen(); + refreshLine(l); + break; + case CTRL_W: /* ctrl+w, delete previous word */ + linenoiseEditDeletePrevWord(l); + break; } - return l.len; + return linenoiseEditMore; +} + +/* This is part of the multiplexed linenoise API. See linenoiseEditStart() + * for more information. This function is called when linenoiseEditFeed() + * returns something different than NULL. At this point the user input + * is in the buffer, and we can restore the terminal in normal mode. */ +void linenoiseEditStop(struct linenoiseState *l) { + if (!isatty(l->ifd)) return; + disableRawMode(l->ifd); + printf("\n"); +} + +/* This just implements a blocking loop for the multiplexed API. + * In many applications that are not event-drivern, we can just call + * the blocking linenoise API, wait for the user to complete the editing + * and return the buffer. */ +static char *linenoiseBlockingEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) +{ + struct linenoiseState l; + + /* Editing without a buffer is invalid. */ + if (buflen == 0) { + errno = EINVAL; + return NULL; + } + + linenoiseEditStart(&l,stdin_fd,stdout_fd,buf,buflen,prompt); + char *res; + while((res = linenoiseEditFeed(&l)) == linenoiseEditMore); + linenoiseEditStop(&l); + return res; } /* This special mode is used by linenoise in order to print scan codes @@ -1003,23 +1144,6 @@ void linenoisePrintKeyCodes(void) { disableRawMode(STDIN_FILENO); } -/* This function calls the line editing function linenoiseEdit() using - * the STDIN file descriptor set in raw mode. */ -static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { - int count; - - if (buflen == 0) { - errno = EINVAL; - return -1; - } - - if (enableRawMode(STDIN_FILENO) == -1) return -1; - count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); - disableRawMode(STDIN_FILENO); - printf("\n"); - return count; -} - /* This function is called when linenoise() is called with the standard * input file descriptor not attached to a TTY. So for example when the * program using linenoise is called in pipe or with a file redirected @@ -1063,7 +1187,6 @@ static char *linenoiseNoTTY(void) { * something even in the most desperate of the conditions. */ char *linenoise(const char *prompt) { char buf[LINENOISE_MAX_LINE]; - int count; if (!isatty(STDIN_FILENO)) { /* Not a tty: read from file / pipe. In this mode we don't want any @@ -1082,9 +1205,8 @@ char *linenoise(const char *prompt) { } return strdup(buf); } else { - count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); - if (count == -1) return NULL; - return strdup(buf); + char *retval = linenoiseBlockingEdit(STDIN_FILENO,STDOUT_FILENO,buf,LINENOISE_MAX_LINE,prompt); + return retval; } } @@ -1093,6 +1215,7 @@ char *linenoise(const char *prompt) { * created with. Useful when the main program is using an alternative * allocator. */ void linenoiseFree(void *ptr) { + if (ptr == linenoiseEditMore) return; // Protect from API misuse. free(ptr); } diff --git a/C/cli/linenoise.h b/C/cli/linenoise.h index 6dfee73b..3f0270e3 100644 --- a/C/cli/linenoise.h +++ b/C/cli/linenoise.h @@ -7,7 +7,7 @@ * * ------------------------------------------------------------------------ * - * Copyright (c) 2010-2014, Salvatore Sanfilippo + * Copyright (c) 2010-2023, Salvatore Sanfilippo * Copyright (c) 2010-2013, Pieter Noordhuis * * All rights reserved. @@ -43,11 +43,48 @@ extern "C" { #endif +#include /* For size_t. */ + +extern char *linenoiseEditMore; + +/* The linenoiseState structure represents the state during line editing. + * We pass this state to functions implementing specific editing + * functionalities. */ +struct linenoiseState { + int in_completion; /* The user pressed TAB and we are now in completion + * mode, so input is handled by completeLine(). */ + size_t completion_idx; /* Index of next completion to propose. */ + int ifd; /* Terminal stdin file descriptor. */ + int ofd; /* Terminal stdout file descriptor. */ + char *buf; /* Edited line buffer. */ + size_t buflen; /* Edited line buffer size. */ + const char *prompt; /* Prompt to display. */ + size_t plen; /* Prompt length. */ + size_t pos; /* Current cursor position. */ + size_t oldpos; /* Previous refresh cursor position. */ + size_t len; /* Current edited line length. */ + size_t cols; /* Number of columns in terminal. */ + size_t oldrows; /* Rows used by last refrehsed line (multiline mode) */ + int history_index; /* The history index we are currently editing. */ +}; + typedef struct linenoiseCompletions { size_t len; char **cvec; } linenoiseCompletions; +/* Non blocking API. */ +int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt); +char *linenoiseEditFeed(struct linenoiseState *l); +void linenoiseEditStop(struct linenoiseState *l); +void linenoiseHide(struct linenoiseState *l); +void linenoiseShow(struct linenoiseState *l); + +/* Blocking API. */ +char *linenoise(const char *prompt); +void linenoiseFree(void *ptr); + +/* Completion API. */ typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold); typedef void(linenoiseFreeHintsCallback)(void *); @@ -56,12 +93,13 @@ void linenoiseSetHintsCallback(linenoiseHintsCallback *); void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); void linenoiseAddCompletion(linenoiseCompletions *, const char *); -char *linenoise(const char *prompt); -void linenoiseFree(void *ptr); +/* History API. */ int linenoiseHistoryAdd(const char *line); int linenoiseHistorySetMaxLen(int len); int linenoiseHistorySave(const char *filename); int linenoiseHistoryLoad(const char *filename); + +/* Other utilities. */ void linenoiseClearScreen(void); void linenoiseSetMultiLine(int ml); void linenoisePrintKeyCodes(void); diff --git a/C/cli/main.c b/C/cli/main.c index fa1d3c26..14570b09 100644 --- a/C/cli/main.c +++ b/C/cli/main.c @@ -26,7 +26,7 @@ #endif #define CLI_HISTORY_FILENAME ".sqlitecloud_history.txt" -#define CLI_VERSION "1.0" +#define CLI_VERSION "1.2" #define CLI_BUILD_DATE __DATE__ #ifndef MAXPATH @@ -114,18 +114,20 @@ static void do_print_usage (void) { printf(" -p PORT port to connect to (default %d)\n", SQCLOUD_DEFAULT_PORT); printf(" -f FILEPATH file path with commands to execute\n"); printf(" -d DATABASE database name\n"); + printf(" -s CONNECTIONSTRING connection string\n"); printf(" -r ROOT_CERTIFICATE path to root certificate for TLS connection\n"); - printf(" -s CLI_CERTIFICATE path to client certificate for TLS connection\n"); - printf(" -t CLI_KEY path to client key certificate for TLS connection\n"); + printf(" -t CLI_CERTIFICATE path to client certificate for TLS connection\n"); + printf(" -k CLI_KEY path to client key certificate for TLS connection\n"); printf(" -u TIMEOUT connection timeout in seconds (default no timeout)\n"); printf(" -y IP connection type (IPv4, IPv6 or IPany, default IPv4)\n"); printf(" -n USERNAME authentication username\n"); printf(" -m PASSWORD authentication password\n"); printf(" -c activate compression\n"); printf(" -i activate insecure mode (non TLS connection)\n"); - printf(" -q activate quite mode (disable output print)\n"); - printf(" -x activate special sqlite mode\n"); + printf(" -j disable certificate verification\n"); + printf(" -q activate quiet mode (disable output print)\n"); printf(" -z request zero-terminated strings in all replies\n"); + printf(" -w in case of -f file to execute, skip the line by line processing and send the whole file\n"); } bool do_print (SQCloudConnection *conn, SQCloudResult *res) { @@ -137,6 +139,7 @@ bool do_print (SQCloudConnection *conn, SQCloudResult *res) { case RESULT_OK: if (skip_ok) return true; printf("OK"); + printf(" (Time: %f secs)", SQCloudResultTime(res)); break; case RESULT_ERROR: @@ -191,7 +194,7 @@ bool do_command_without_ok_reply (SQCloudConnection *conn, char *command) { bool do_internal_command (SQCloudConnection *conn, char *command); -bool do_process_file (SQCloudConnection *conn, const char *filename) { +bool do_process_file (SQCloudConnection *conn, const char *filename, bool linebyline) { // should continue flag set to false by default bool should_continue = false; @@ -201,14 +204,34 @@ bool do_process_file (SQCloudConnection *conn, const char *filename) { return false; } - char line[512]; - while (fgets(line, sizeof(line), file)) { - line[strcspn(line, "\n")] = 0; - if (strcasecmp(line, ".PROMPT")==0) {should_continue = true; break;} - printf(">> %s\n", line); - (line[0] == '.') ? do_internal_command(conn, line) : do_command(conn, line); + if (linebyline) { + char line[512]; + while (fgets(line, sizeof(line), file)) { + line[strcspn(line, "\n")] = 0; + if (strcasecmp(line, ".PROMPT")==0) {should_continue = true; break;} + printf(">> %s\n", line); + (line[0] == '.') ? do_internal_command(conn, line) : do_command(conn, line); + } + } else { + // get file size + fseek(file, 0, SEEK_END); + long size = ftell(file); + fseek(file, 0, SEEK_SET); + + char *buffer = malloc(size + 1); + if (!buffer) {printf("Unable to allocate %ld buffer size.\n", size); goto cleanup;} + + size_t nread = fread(buffer, 1, size, file); + if (nread != size) {printf("An error occurred while reading file %s (%ld - %zu).\n", filename, size, nread); free(buffer); goto cleanup;} + buffer[size] = 0; + + printf(">> Executing file: %s (%ld bytes)\n\n", filename, size); + + do_command(conn, buffer); + free(buffer); } +cleanup: fclose(file); return should_continue; } @@ -288,7 +311,7 @@ int do_internal_read_cb (void *xdata, void *buffer, uint32_t *blen, int64_t ntot if (nread == -1) return -1; if (nread == 0) printf("UPLOAD COMPLETE\n\n"); - else printf("%.2f%% ", ((double)(nprogress+nread) / (double)ntot) * 100.0); + else { printf("%.2f%% ", ((double)(nprogress+nread) / (double)ntot) * 100.0); fflush(stdout); } *blen = (uint32_t)nread; return 0; @@ -348,6 +371,15 @@ bool do_internal_upload (SQCloudConnection *conn, char *command) { return result; } +bool do_internal_file (SQCloudConnection *conn, char *command) { + // .file path_to_file + + // skip command name part + command += strlen(".file "); + + return do_process_file(conn, command, false); +} + bool do_internal_prepare (SQCloudConnection *conn, char *command) { // .prepare sql @@ -429,6 +461,7 @@ bool do_internal_command (SQCloudConnection *conn, char *command) { if (strcmp(cname, ".download") == 0) return do_internal_download(conn, command); if (strcmp(cname, ".upload") == 0) return do_internal_upload(conn, command); + if (strcmp(cname, ".file") == 0) return do_internal_file(conn, command); if ((strcmp(cname, ".prepare") == 0) || (strcmp(cname, ".step") == 0) || (strcmp(cname, ".clear") == 0) || (strcmp(cname, ".reset") == 0) || (strcmp(cname, ".finalize") == 0)) { @@ -457,6 +490,7 @@ int main(int argc, char * argv[]) { const char *password = NULL; const char *filename = NULL; const char *database = NULL; + const char *connstring = NULL; const char *root_certificate_path = NULL; const char *client_certificate_path = NULL; const char *client_certificate_key_path = NULL; @@ -467,11 +501,12 @@ int main(int argc, char * argv[]) { bool compression = false; bool insecure = false; - bool sqlite = false; + bool noverifycert = false; bool zerotext = false; + bool linebyline = true; int c; - while ((c = getopt (argc, argv, "h:p:f:vciqxzr:s:t:d:y:u:n:m:")) != -1) { + while ((c = getopt (argc, argv, "h:p:f:vcijqxwzr:s:t:d:y:u:n:m:")) != -1) { switch (c) { case 'v': do_print_usage(); return 0; case 'h': hostname = optarg; break; @@ -479,20 +514,22 @@ int main(int argc, char * argv[]) { case 'f': filename = optarg; break; case 'c': compression = true; break; case 'i': insecure = true; break; + case 'j': noverifycert = true; break; case 'q': quiet = true; break; - case 'x': sqlite = true; break; case 'z': zerotext = true; break; + case 'w': linebyline = false; break; case 'd': database = optarg; break; case 'r': root_certificate_path = optarg; break; - case 's': client_certificate_path = optarg; break; - case 't': client_certificate_key_path = optarg; break; + case 't': client_certificate_path = optarg; break; + case 'k': client_certificate_key_path = optarg; break; case 'u': timeout = atoi(optarg); break; case 'y': if (strcasestr(optarg, "IPv6") != 0) {family = SQCLOUD_IPv6;} - else if (strcasestr(optarg, "IPany") != 0) {family = SQCLOUD_IPany;} + else if (strcasestr(optarg, "IPany") != 0) {family = SQCLOUD_IPANY;} break; case 'n': username = optarg; break; case 'm': password = optarg; break; + case 's': connstring = optarg; break; } } @@ -509,35 +546,41 @@ int main(int argc, char * argv[]) { config.family = family; config.timeout = timeout; - // setup TLS config parameter - #ifndef SQLITECLOUD_DISABLE_TSL - if (insecure) config.insecure = true; - if (root_certificate_path) config.tls_root_certificate = root_certificate_path; - if (client_certificate_path) config.tls_certificate = client_certificate_path; - if (client_certificate_key_path) config.tls_certificate_key = client_certificate_key_path; - #endif - - if (sqlite) config.sqlite_mode = true; - if (zerotext) config.zero_text = true; - if (compression) config.compression = true; - if (database) config.database = database; - if (username) config.username = username; - if (password) config.password = password; - // try to connect to hostname:port - SQCloudConnection *conn = SQCloudConnect(hostname, port, &config); + SQCloudConnection *conn = NULL; + if (connstring) { + conn = SQCloudConnectWithString(connstring, &config); + } else { + // setup TLS config parameter + #ifndef SQLITECLOUD_DISABLE_TLS + if (insecure) config.insecure = true; + if (noverifycert) config.no_verify_certificate = true; + if (root_certificate_path) config.tls_root_certificate = root_certificate_path; + if (client_certificate_path) config.tls_certificate = client_certificate_path; + if (client_certificate_key_path) config.tls_certificate_key = client_certificate_key_path; + #endif + + if (zerotext) config.zero_text = true; + if (compression) config.compression = true; + if (database) config.database = database; + if (username) config.username = username; + if (password) config.password = password; + + conn = SQCloudConnect(hostname, port, &config); + } + if (SQCloudIsError(conn)) { printf("ERROR connecting to %s: %s (%d)\n", hostname, SQCloudErrorMsg(conn), SQCloudErrorCode(conn)); return -1; } else { - printf("Connection to %s OK...\n\n", hostname); + if (!quiet) printf("Connection to %s:%d OK...\n\n", hostname, port); } // load history file linenoiseHistoryLoad(CLI_HISTORY_FILENAME); if (filename) { - bool should_continue = do_process_file(conn, filename); + bool should_continue = do_process_file(conn, filename, linebyline); if (should_continue == false) return 0; } diff --git a/C/cli/sqlitecloud-cli.xcodeproj/project.pbxproj b/C/cli/sqlitecloud-cli.xcodeproj/project.pbxproj index a397b09e..f7970308 100644 --- a/C/cli/sqlitecloud-cli.xcodeproj/project.pbxproj +++ b/C/cli/sqlitecloud-cli.xcodeproj/project.pbxproj @@ -264,6 +264,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = "-"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; CODE_SIGN_STYLE = Manual; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 3ZH6236ET5; @@ -272,7 +273,7 @@ LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../SSL/macos_fat\""; MACOSX_DEPLOYMENT_TARGET = 12.3; OTHER_CODE_SIGN_FLAGS = "--timestamp"; - PRODUCT_BUNDLE_IDENTIFIER = "com.sqlabs.sqlitecloud-cli"; + PRODUCT_BUNDLE_IDENTIFIER = "io.sqlitecloud.sqlitecloud-cli-c"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; }; @@ -282,6 +283,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = "-"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; CODE_SIGN_STYLE = Manual; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 3ZH6236ET5; @@ -290,7 +292,7 @@ LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../SSL/macos_fat\""; MACOSX_DEPLOYMENT_TARGET = 12.3; OTHER_CODE_SIGN_FLAGS = "--timestamp"; - PRODUCT_BUNDLE_IDENTIFIER = "com.sqlabs.sqlitecloud-cli"; + PRODUCT_BUNDLE_IDENTIFIER = "io.sqlitecloud.sqlitecloud-cli-c"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; }; diff --git a/C/sqcloud.c b/C/sqcloud.c index c33e5f99..01e44003 100644 --- a/C/sqcloud.c +++ b/C/sqcloud.c @@ -40,8 +40,36 @@ #include #endif -#ifndef SQLITECLOUD_DISABLE_TSL +#ifndef SQLITECLOUD_DISABLE_TLS + +#ifdef SQLITECLOUD_USE_TLS_HEADER #include "tls.h" +#else +#ifndef HEADER_TLS_H +#define TLS_WANT_POLLIN -2 +#define TLS_WANT_POLLOUT -3 +struct tls; +struct tls_config; +struct tls *tls_client(void); +struct tls_config *tls_config_new(void); +void tls_config_free(struct tls_config *config); +int tls_init(void); +int tls_configure(struct tls *_ctx, struct tls_config *_config); +int tls_connect_socket(struct tls *_ctx, int _s, const char *_servername); +int tls_close(struct tls *_ctx); +void tls_config_insecure_noverifycert(struct tls_config *config); +void tls_config_insecure_noverifyname(struct tls_config *config); +int tls_config_set_ca_file(struct tls_config *_config, const char *_ca_file); +int tls_config_set_cert_file(struct tls_config *_config,const char *_cert_file); +int tls_config_set_key_file(struct tls_config *_config, const char *_key_file); +ssize_t tls_read(struct tls *_ctx, void *_buf, size_t _buflen); +ssize_t tls_write(struct tls *_ctx, const void *_buf, size_t _buflen); +const char *tls_error(struct tls *_ctx); +const char *tls_config_error(struct tls_config *_config); +void tls_free(struct tls *_ctx); +#endif +#endif + #endif // MARK: MACROS - @@ -62,6 +90,7 @@ #define mem_alloc(_s) malloc(_s) #define mem_free(_s) free(_s) #define mem_string_dup(_s) strdup(_s) +#define mem_string_ndup(_s,_n) strndup(_s,_n) #endif #ifndef MIN #define MIN(a,b) (((a)<(b))?(a):(b)) @@ -125,6 +154,7 @@ struct SQCloudResult { }; struct { char **buffers; // array of buffers used by rowset sent in chunk + bool *bext; // array of flags, if true the buffer must not be freed uint32_t *blens; // array of buffer len uint32_t *nheads; // array of header len uint32_t bcount; // number of buffers in the array @@ -141,16 +171,19 @@ struct SQCloudResult { uint32_t nheader; // number of character in the first part of the header (which is usually skipped) // used in TYPE_ROWSET only - uint32_t flags; // rowset flags + uint32_t version; // rowset version uint32_t nrows; // number of rows uint32_t ncols; // number of columns uint32_t ndata; // number of items stores in data char **data; // data contained in the rowset char **name; // column names - char **decltype; // column declared types (sqlite mode only) - char **dbname; // column database names (sqlite mode only) - char **tblname; // column table names (sqlite mode only) - char **origname; // column origin names (sqlite mode only) + char **decltype; // column declared types + char **dbname; // column database names + char **tblname; // column table names + char **origname; // column origin names + int *notnull; // column is not null + int *prikey; // column is primary key + int *autoinc; // column is auto increment uint32_t *clen; // max len for each column (used to display result) uint32_t maxlen; // max len for each row/column @@ -182,7 +215,7 @@ struct SQCloudConnection { int port; pthread_t tid; - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS struct tls *tls_context; struct tls *tls_pubsub_context; #endif @@ -196,7 +229,7 @@ struct SQCloudVM { bool finalized; bool isreadonly; - bool isexplain; + int isexplain; int rowindex; int nparams; int ncolumns; @@ -315,7 +348,7 @@ static void *pubsub_thread (void *arg) { SQCloudConnection *connection = (SQCloudConnection *)arg; int fd = connection->pubsubfd; - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS struct tls *tls = connection->tls_pubsub_context; #endif @@ -336,7 +369,7 @@ static void *pubsub_thread (void *arg) { if (rc <= 0) continue; // read payload string - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS ssize_t nread = (tls) ? tls_read(tls, buffer, blen) : readsocket(fd, buffer, blen); if ((tls) && (nread == TLS_WANT_POLLIN || nread == TLS_WANT_POLLOUT)) continue; #else @@ -345,23 +378,18 @@ static void *pubsub_thread (void *arg) { if (nread < 0) { const char *msg = ""; - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS if (tls) msg = tls_error(tls); #endif internal_set_error(connection, INTERNAL_ERRCODE_NETWORK, "An error occurred while reading data: %s (%s).", strerror(errno), msg); - connection->callback(connection, NULL, connection->data); + if (connection->callback) connection->callback(connection, NULL, connection->data); break; } if (nread == 0) { - const char *msg = ""; - #ifndef SQLITECLOUD_DISABLE_TSL - if (tls) msg = tls_error(tls); - #endif - - internal_set_error(connection, INTERNAL_ERRCODE_NETWORK, "An error occurred while reading data: %s (%s).", strerror(errno), msg); - connection->callback(connection, NULL, connection->data); + internal_set_error(connection, INTERNAL_ERRCODE_SOCKCLOSED, "PubSub connection closed."); + if (connection->callback) connection->callback(connection, NULL, connection->data); break; } @@ -383,7 +411,7 @@ static void *pubsub_thread (void *arg) { char *clone = mem_alloc(clen + cstart + 1); if (!clone) { internal_set_error(connection, INTERNAL_ERRCODE_MEMORY, "Unable to allocate memory: %d.", clen + cstart + 1); - connection->callback(connection, NULL, connection->data); + if (connection->callback) connection->callback(connection, NULL, connection->data); break; } memcpy(clone, original, tread); @@ -397,6 +425,7 @@ static void *pubsub_thread (void *arg) { SQCloudResult *result = internal_parse_buffer(connection, original, tread, (clen) ? cstart : 0, false, false); if (result->tag == RESULT_STRING) result->tag = RESULT_JSON; + if (!connection->callback) break; connection->callback(connection, result, connection->data); @@ -408,6 +437,7 @@ static void *pubsub_thread (void *arg) { tread = 0; } + if (buffer) mem_free(buffer); return NULL; } @@ -445,27 +475,6 @@ static bool internal_set_error (SQCloudConnection *connection, int errcode, cons return false; } -static void internal_parse_uuid (SQCloudConnection *connection, const char *buffer, size_t blen) { - // sanity check - if (!buffer || blen == 0) return; - - // expected buffer is PAUTH uuid secret - // PUATH -> 5 - // uuid -> 36 - // secret -> 36 - // spaces -> 2 - if (blen != (5 + 36 + 36 + 2)) return; - - if (strncmp(buffer, "PAUTH ", 6) != 0) return; - - // allocate 36 (UUID) + 1 (null-terminated) zero-bytes - char *uuid = mem_zeroalloc(37); - if (!uuid) return; - - memcpy(uuid, &buffer[6], 36); - connection->uuid = uuid; -} - static void internal_clear_error (SQCloudConnection *connection) { connection->errcode = 0; connection->extcode = 0; @@ -474,7 +483,7 @@ static void internal_clear_error (SQCloudConnection *connection) { } static bool internal_setup_tls (SQCloudConnection *connection, SQCloudConfig *config, bool mainfd) { - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS if (config && config->insecure) return true; int rc = 0; @@ -488,6 +497,11 @@ static bool internal_setup_tls (SQCloudConnection *connection, SQCloudConfig *co return internal_set_error(connection, INTERNAL_ERRCODE_TLS, "Error while initializing a new TLS configuration."); } + if (config->no_verify_certificate) { + tls_config_insecure_noverifycert(tls_conf); + tls_config_insecure_noverifyname(tls_conf); + } + // loads a file containing the root certificates if (config && config->tls_root_certificate) { rc = tls_config_set_ca_file(tls_conf, config->tls_root_certificate); @@ -518,6 +532,7 @@ static bool internal_setup_tls (SQCloudConnection *connection, SQCloudConfig *co // apply configuration to context rc = tls_configure(tls_context, tls_conf); + tls_config_free(tls_conf); if (rc < 0) { return internal_set_error(connection, INTERNAL_ERRCODE_TLS, "Error in tls_configure: %s.", tls_error(tls_context)); } @@ -701,7 +716,6 @@ static SQCloudResult *internal_setup_pubsub (SQCloudConnection *connection, cons if (internal_connect(connection, connection->hostname, connection->port, connection->_config, false)) { SQCloudResult *result = internal_run_command(connection, buffer, blen, false); if (!SQCloudResultIsOK(result)) return result; - internal_parse_uuid(connection, buffer, blen); pthread_create(&connection->tid, NULL, pubsub_thread, (void *)connection); } else { return NULL; @@ -757,7 +771,8 @@ static SQCloudResult *internal_parse_array (SQCloudConnection *connection, char // loop from i to n to parse each data buffer += bstart + start1; for (uint32_t i=0; idata[i] = (value) ? buffer : NULL; buffer += cellsize; @@ -793,12 +808,20 @@ static char *internal_get_rowset_header (SQCloudResult *result, char **header, u return internal_parse_value(header[col], len, NULL); } -static bool internal_parse_rowset_header (SQCloudResult *rowset, char **pbuffer, uint32_t *pblen, uint32_t ncols, uint32_t flags) { - if (BITCHECK(flags, SQCLOUD_ROWSET_FLAG_DATAONLY)) return true; +static int internal_get_rowset_header_int (SQCloudResult *result, int *header, uint32_t col) { + if (!result || result->tag != RESULT_ROWSET) return -1; + if (col >= result->ncols) return -1; + if (header == NULL) return -1; + return header[col]; +} + +static bool internal_parse_rowset_header (SQCloudResult *rowset, char **pbuffer, uint32_t *pblen, uint32_t ncols, uint32_t version) { + if (version == ROWSET_TYPE_DATA_ONLY) return true; char *buffer = *pbuffer; uint32_t blen = *pblen; + /* if (BITCHECK(flags, SQCLOUD_ROWSET_FLAG_METAVM)) { uint32_t cstart1 = 0, cstart2 = 0, cstart3 = 0, cstart4 = 0, cstart5 = 0; @@ -830,8 +853,9 @@ static bool internal_parse_rowset_header (SQCloudResult *rowset, char **pbuffer, rowset->n4 = n4; rowset->n5 = n5; } + */ - // header is guarantee to contains column names (1st) + // header is guarantee to contain column names for (uint32_t i=0; imaxlen < len) rowset->maxlen = len; } - if (BITCHECK(flags, SQCLOUD_ROWSET_FLAG_METACOLS)) { + // check if additional metadata is contained + if (version == ROWSET_TYPE_METADATA_v1) { rowset->decltype = (char **) mem_alloc(ncols * sizeof(char *)); if (!rowset->decltype) return false; rowset->dbname = (char **) mem_alloc(ncols * sizeof(char *)); @@ -851,8 +876,14 @@ static bool internal_parse_rowset_header (SQCloudResult *rowset, char **pbuffer, if (!rowset->tblname) return false; rowset->origname = (char **) mem_alloc(ncols * sizeof(char *)); if (!rowset->origname) return false; - - // in sqlite mode header contains column declared types (2nd) + rowset->notnull = (int *) mem_alloc(ncols * sizeof(int)); + if (!rowset->notnull) return false; + rowset->prikey = (int *) mem_alloc(ncols * sizeof(int)); + if (!rowset->prikey) return false; + rowset->autoinc = (int *) mem_alloc(ncols * sizeof(int)); + if (!rowset->autoinc) return false; + + // column declared types for (uint32_t i=0; inotnull[i] = (int)value; + uint32_t len = 0; + buffer += cstart + len + 1; + blen -= cstart + len + 1; + } + + // column primary key flag + for (uint32_t i=0; iprikey[i] = (int)value; + uint32_t len = 0; + buffer += cstart + len + 1; + blen -= cstart + len + 1; + } + + // column autoincrement key flag + for (uint32_t i=0; iautoinc[i] = (int)value; + uint32_t len = 0; + buffer += cstart + len + 1; + blen -= cstart + len + 1; + } } *pbuffer = buffer; @@ -895,8 +956,8 @@ static bool internal_parse_rowset_header (SQCloudResult *rowset, char **pbuffer, return true; } -static bool internal_parse_rowset_values (SQCloudResult *rowset, char **pbuffer, uint32_t *pblen, uint32_t index, uint32_t bound, uint32_t ncols, uint32_t flags) { - if (BITCHECK(flags, SQCLOUD_ROWSET_FLAG_HEADONLY)) return true; +static bool internal_parse_rowset_values (SQCloudResult *rowset, char **pbuffer, uint32_t *pblen, uint32_t index, uint32_t bound, uint32_t ncols, uint32_t version) { + if (version == ROWSET_TYPE_HEADER_ONLY) return true; char *buffer = *pbuffer; uint32_t blen = *pblen; @@ -916,7 +977,7 @@ static bool internal_parse_rowset_values (SQCloudResult *rowset, char **pbuffer, } static SQCloudResult *internal_parse_rowset (SQCloudConnection *connection, char *buffer, uint32_t blen, uint32_t bstart, - uint32_t nrows, uint32_t ncols, uint32_t flags) { + uint32_t nrows, uint32_t ncols, uint32_t version) { SQCloudResult *rowset = (SQCloudResult *)mem_zeroalloc(sizeof(SQCloudResult)); if (!rowset) { internal_set_error(connection, INTERNAL_ERRCODE_MEMORY, "Unable to allocate memory for SQCloudResult: %d.", sizeof(SQCloudResult)); @@ -929,7 +990,7 @@ static SQCloudResult *internal_parse_rowset (SQCloudConnection *connection, char rowset->blen = blen; rowset->balloc = blen; rowset->nheader = bstart; - rowset->flags = flags; + rowset->version = version; rowset->nrows = nrows; rowset->ncols = ncols; @@ -942,10 +1003,10 @@ static SQCloudResult *internal_parse_rowset (SQCloudConnection *connection, char blen -= bstart; // parse rowset header - if (!internal_parse_rowset_header(rowset, &buffer, &blen, ncols, flags)) goto abort_rowset; + if (!internal_parse_rowset_header(rowset, &buffer, &blen, ncols, version)) goto abort_rowset; // parse values (buffer and blen was updated in internal_parse_rowset_header) - if (!internal_parse_rowset_values(rowset, &buffer, &blen, 0, nrows * ncols, ncols, flags)) goto abort_rowset; + if (!internal_parse_rowset_values(rowset, &buffer, &blen, 0, nrows * ncols, ncols, version)) goto abort_rowset; return rowset; @@ -960,7 +1021,7 @@ static SQCloudResult *internal_parse_rowset (SQCloudConnection *connection, char } static SQCloudResult *internal_parse_rowset_chunck (SQCloudConnection *connection, char *buffer, uint32_t blen, uint32_t bstart, uint32_t idx, - uint32_t nrows, uint32_t ncols, uint32_t flags) { + uint32_t nrows, uint32_t ncols, uint32_t version) { SQCloudResult *rowset = connection->_chunk; bool first_chunk = false; @@ -989,12 +1050,15 @@ static SQCloudResult *internal_parse_rowset_chunck (SQCloudConnection *connectio if (first_chunk) { rowset->tag = RESULT_ROWSET; - rowset->flags = flags; + rowset->version = version; rowset->ischunk = true; rowset->buffers = (char **)mem_zeroalloc((sizeof(char *) * DEFAULT_CHUCK_NBUFFERS)); if (!rowset->buffers) goto abort_rowset; + rowset->bext = (bool *)mem_zeroalloc((sizeof(bool) * DEFAULT_CHUCK_NBUFFERS)); + if (!rowset->bext) goto abort_rowset; + rowset->blens = (uint32_t *)mem_zeroalloc((sizeof(uint32_t) * DEFAULT_CHUCK_NBUFFERS)); if (!rowset->blens) goto abort_rowset; @@ -1003,6 +1067,7 @@ static SQCloudResult *internal_parse_rowset_chunck (SQCloudConnection *connectio rowset->bnum = DEFAULT_CHUCK_NBUFFERS; rowset->buffers[0] = buffer; + rowset->bext[0] = false; rowset->blens[0] = blen; rowset->nheads[0] = bstart; rowset->bcount = 1; @@ -1018,7 +1083,7 @@ static SQCloudResult *internal_parse_rowset_chunck (SQCloudConnection *connectio buffer += bstart; // parse rowset header - if (!internal_parse_rowset_header(rowset, &buffer, &blen, ncols, flags)) goto abort_rowset; + if (!internal_parse_rowset_header(rowset, &buffer, &blen, ncols, version)) goto abort_rowset; } // update total buffer size @@ -1038,6 +1103,10 @@ static SQCloudResult *internal_parse_rowset_chunck (SQCloudConnection *connectio if (!temp) goto abort_rowset; rowset->buffers = temp; + bool *temp1 = (bool*)mem_realloc(rowset->bext, (sizeof(bool) * n)); + if (!temp1) goto abort_rowset; + rowset->bext = temp1; + uint32_t *temp2 = (uint32_t*)mem_realloc(rowset->blens, (sizeof(uint32_t) * n)); if (!temp2) goto abort_rowset; rowset->blens = temp2; @@ -1061,6 +1130,7 @@ static SQCloudResult *internal_parse_rowset_chunck (SQCloudConnection *connectio // adjust internal fields if (!first_chunk) { rowset->buffers[rowset->bcount] = buffer; + rowset->bext[rowset->bcount] = rowset->externalbuffer; rowset->blens[rowset->bcount] = blen; rowset->nheads[rowset->bcount] = bstart; rowset->nrows += nrows; @@ -1075,14 +1145,16 @@ static SQCloudResult *internal_parse_rowset_chunck (SQCloudConnection *connectio uint32_t bound = rowset->ndata + (nrows * ncols); // parse values - if (!internal_parse_rowset_values(rowset, &buffer, &blen, index, bound, ncols, flags)) goto abort_rowset; + if (!internal_parse_rowset_values(rowset, &buffer, &blen, index, bound, ncols, version)) goto abort_rowset; // this check is for internal usage only if (connection->fd == 0) return rowset; - // normal usage + #if 0 + // January 24th, 2024 -> ACK disabled for Rowset in chunks // send ACK - if (!internal_socket_write(connection, "OK", 2, true, true)) goto abort_rowset; + // if (!internal_socket_write(connection, "OK", 2, true, true)) goto abort_rowset; + #endif // read next chunk return internal_socket_read (connection, true); @@ -1096,23 +1168,14 @@ static SQCloudResult *internal_parse_rowset_chunck (SQCloudConnection *connectio static SQCloudResult *internal_parse_buffer (SQCloudConnection *connection, char *buffer, uint32_t blen, uint32_t cstart, bool isstatic, bool externalbuffer) { if (blen <= 1) return false; + bool buffer_canbe_freed = (!isstatic && !externalbuffer); + // try to check if it is a OK reply: +2 OK if ((blen == REPLY_OK_LEN) && (strncmp(buffer, REPLY_OK, REPLY_OK_LEN) == 0)) { + if (buffer_canbe_freed) mem_free(buffer); return &SQCloudResultOK; } - // if buffer is static (stack based allocation) then it must be duplicated - if (buffer[0] != CMD_ERROR && isstatic) { - char *clone = mem_alloc(blen); - if (!clone) { - internal_set_error(connection, INTERNAL_ERRCODE_MEMORY, "Unable to allocate memory: %d.", blen); - return NULL; - } - memcpy(clone, buffer, blen); - buffer = clone; - isstatic = false; - } - // check for compressed reply before the parse step char *zdata = NULL; if (buffer[0] == CMD_COMPRESSED) { @@ -1131,11 +1194,12 @@ static SQCloudResult *internal_parse_buffer (SQCloudConnection *connection, char char *hstart = &buffer[cstart1 + cstart2 + cstart3 + 1]; // try to allocate a buffer big enough to hold uncompressed data + raw header - uint32_t clonelen = ulen + (uint32_t)(hstart - buffer); + // 256 is an arbitrary memory cushion value + uint32_t clonelen = ulen + (uint32_t)(hstart - buffer) + 256; char *clone = mem_alloc (clonelen); if (!clone) { internal_set_error(connection, INTERNAL_ERRCODE_MEMORY, "Unable to allocate memory to uncompress buffer: %d.", clonelen); - if (!isstatic && !externalbuffer) mem_free(buffer); + if (buffer_canbe_freed) mem_free(buffer); return NULL; } @@ -1146,12 +1210,12 @@ static SQCloudResult *internal_parse_buffer (SQCloudConnection *connection, char uint32_t rc = LZ4_decompress_safe(zdata, clone + (zdata - hstart), clen, ulen); if (rc <= 0 || rc != ulen) { internal_set_error(connection, INTERNAL_ERRCODE_GENERIC, "Unable to decompress buffer (err code: %d).", rc); - if (!isstatic && !externalbuffer) mem_free(buffer); + if (buffer_canbe_freed) mem_free(buffer); return NULL; } // decompression is OK so replace buffer - if (!isstatic && !externalbuffer) mem_free(buffer); + if (buffer_canbe_freed) mem_free(buffer); isstatic = false; buffer = clone; @@ -1160,8 +1224,25 @@ static SQCloudResult *internal_parse_buffer (SQCloudConnection *connection, char // at this point the buffer used in the SQCloudResult is a newly allocated one (clone) // so externalbuffer flag must be set to false externalbuffer = false; + } else { + // if buffer is static (stack based allocation) then it must be duplicated + bool buffer_should_be_duplicated = (buffer[0] != CMD_ERROR); + if (buffer_should_be_duplicated && isstatic) { + char *clone = mem_alloc(blen); + if (!clone) { + internal_set_error(connection, INTERNAL_ERRCODE_MEMORY, "Unable to allocate memory: %d.", blen); + if (buffer_canbe_freed) mem_free(buffer); + return NULL; + } + memcpy(clone, buffer, blen); + buffer = clone; + isstatic = false; + } } + // re-compute flag + buffer_canbe_freed = (!isstatic && !externalbuffer); + // parse reply switch (buffer[0]) { case CMD_ZEROSTRING: @@ -1205,7 +1286,7 @@ static SQCloudResult *internal_parse_buffer (SQCloudConnection *connection, char connection->errmsg[len] = 0; // check free buffer - if (!isstatic && !externalbuffer) mem_free(buffer); + if (buffer_canbe_freed) mem_free(buffer); return NULL; } @@ -1214,10 +1295,10 @@ static SQCloudResult *internal_parse_buffer (SQCloudConnection *connection, char // CMD_ROWSET: *LEN 0:VERSION ROWS COLS DATA // CMD_ROWSET_CHUNK: /LEN IDX:VERSION ROWS COLS DATA uint32_t cstart1 = 0, cstart2 = 0, cstart3 = 0, cstart4 = 0; - uint32_t flags = 0; + uint32_t version = 0; internal_parse_number(&buffer[1], blen-1, &cstart1); // parse len (already parsed in blen parameter) - uint32_t idx = internal_parse_number_extended(&buffer[cstart1 + 1], blen-(cstart1+1), &cstart2, &flags, NULL); + uint32_t idx = internal_parse_number_extended(&buffer[cstart1 + 1], blen-(cstart1+1), &cstart2, &version, NULL); uint32_t nrows = internal_parse_number(&buffer[cstart1 + cstart2 + 1], blen-(cstart1 + cstart2 + 1), &cstart3); uint32_t ncols = internal_parse_number(&buffer[cstart1 + cstart2 + + cstart3 + 1], blen-(cstart1 + cstart2 + + cstart3 + 1), &cstart4); @@ -1227,17 +1308,20 @@ static SQCloudResult *internal_parse_buffer (SQCloudConnection *connection, char SQCloudResult *res = NULL; // the externalbuffer flag can change in case of compressed rowset when the end chunk is received if (connection->_chunk) connection->_chunk->externalbuffer = externalbuffer; - if (buffer[0] == CMD_ROWSET) res = internal_parse_rowset(connection, buffer, blen, bstart, nrows, ncols, flags); - else res = internal_parse_rowset_chunck(connection, buffer, blen, bstart, idx, nrows, ncols, flags); - if (res) res->externalbuffer = externalbuffer; + if (buffer[0] == CMD_ROWSET) res = internal_parse_rowset(connection, buffer, blen, bstart, nrows, ncols, version); + else res = internal_parse_rowset_chunck(connection, buffer, blen, bstart, idx, nrows, ncols, version); + if (res) { + res->externalbuffer = externalbuffer; + if (res->ischunk && res->bcount == 1) res->bext[0] = externalbuffer; + } // check free buffer - if (!res && !isstatic && !externalbuffer) mem_free(buffer); + if (!res && buffer_canbe_freed) mem_free(buffer); return res; } case CMD_NULL: - if (!isstatic && !externalbuffer) mem_free(buffer); + if (buffer_canbe_freed) mem_free(buffer); return &SQCloudResultNULL; case CMD_INT: @@ -1247,7 +1331,7 @@ static SQCloudResult *internal_parse_buffer (SQCloudConnection *connection, char SQCloudResult *res = internal_rowset_type(connection, buffer, blen, 1, (buffer[0] == CMD_INT) ? RESULT_INTEGER : RESULT_FLOAT); if (res) res->externalbuffer = externalbuffer; - if (!res && !isstatic && !externalbuffer) mem_free(buffer); + if (!res && buffer_canbe_freed) mem_free(buffer); return res; } @@ -1259,7 +1343,7 @@ static SQCloudResult *internal_parse_buffer (SQCloudConnection *connection, char } } - if (!isstatic && !externalbuffer) mem_free(buffer); + if (buffer_canbe_freed) mem_free(buffer); return NULL; } @@ -1269,172 +1353,194 @@ static bool internal_socket_forward_read (SQCloudConnection *connection, bool (* uint32_t cstart = 0; uint32_t tread = 0; uint32_t clen = 0; + char type = 0; + ssize_t nread = 0; char *buffer = sbuffer; char *original = buffer; int fd = connection->fd; - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS struct tls *tls = connection->tls_context; #endif while (1) { // perform read operation - #ifndef SQLITECLOUD_DISABLE_TSL - ssize_t nread = (tls) ? tls_read(tls, buffer, blen) : readsocket(fd, buffer, blen); + #ifndef SQLITECLOUD_DISABLE_TLS + nread = (tls) ? tls_read(tls, buffer, blen) : readsocket(fd, buffer, blen); if ((tls) && (nread == TLS_WANT_POLLIN || nread == TLS_WANT_POLLOUT)) continue; #else - ssize_t nread = readsocket(fd, buffer, blen); + nread = readsocket(fd, buffer, blen); #endif + if (nread == -1 && errno == EINTR) continue; // sanity check read - if (nread < 0) { - const char *msg = ""; - #ifndef SQLITECLOUD_DISABLE_TSL - if (tls) msg = tls_error(tls); - #endif - - internal_set_error(connection, INTERNAL_ERRCODE_NETWORK, "An error occurred while reading data: %s (%s).", strerror(errno), msg); - goto abort_read; - } - - if (nread == 0) { - const char *msg = ""; - #ifndef SQLITECLOUD_DISABLE_TSL - if (tls) msg = tls_error(tls); - #endif - - internal_set_error(connection, INTERNAL_ERRCODE_NETWORK, "Unexpected EOF found while reading data: %s (%s).", strerror(errno), msg); - goto abort_read; - } + if (nread <= 0) goto abort_read; // forward read to callback bool result = forward_cb(buffer, nread, xdata, xdata2); if (!result) goto abort_read; - // update internal counter - tread += (uint32_t)nread; + // read original type + if (type == 0) type = buffer[0]; - // determine command length - if (clen == 0) { - clen = internal_parse_number (&original[1], tread-1, &cstart); + if (type != CMD_ROWSET_CHUNK) { + // update internal counter + tread += (uint32_t)nread; + + // determine command length + if (clen == 0) { + clen = internal_parse_number (&original[1], tread-1, &cstart); + + // handle special cases + if ((original[0] == CMD_INT) || (original[0] == CMD_FLOAT) || (original[0] == CMD_NULL)) clen = 0; + else if (clen == 0) continue; + } - // handle special cases - if ((original[0] == CMD_INT) || (original[0] == CMD_FLOAT) || (original[0] == CMD_NULL)) clen = 0; - else if (clen == 0) continue; + // check if read is complete + if (clen + cstart + 1 == tread) break; + } else { + const char *end_of_chunk = "/6 0 0 0 "; + size_t end_of_chunk_len = 9; + + if (nread >= end_of_chunk_len) { + if (strncmp(buffer + nread - end_of_chunk_len, end_of_chunk, end_of_chunk_len) == 0) break; + } else { + // there is an extremely rare possibility that the end of chuck was split by the TCP driver + // in that case we would have no way to determine the end of the rowset chunk + ; + } } - - // check if read is complete - if (clen + cstart + 1 == tread) break; } return true; -abort_read: +abort_read: { + const char *msg = ""; + const char *format = (nread == 0) ? "Unexpected EOF found while reading data: %s (%s)." : "An error occurred while reading data: %s (%s)."; + #ifndef SQLITECLOUD_DISABLE_TLS + if (tls) msg = tls_error(tls); + #endif + internal_set_error(connection, INTERNAL_ERRCODE_NETWORK, format, strerror(errno), msg); + } return false; } -static SQCloudResult *internal_socket_read (SQCloudConnection *connection, bool mainfd) { - // most of the time one read will be sufficient - char header[4096]; - char *buffer = (char *)&header; - uint32_t blen = sizeof(header); - uint32_t tread = 0; +static ssize_t internal_socket_read_nbytes (int fd, void *tlsp, char *buffer, ssize_t len) { + ssize_t total_read = 0; - uint32_t cstart = 0; - uint32_t clen = 0; + while (1) { + #ifndef SQLITECLOUD_DISABLE_TLS + ssize_t nread = (tlsp) ? tls_read((struct tls *)tlsp, buffer + total_read, len - total_read) : readsocket(fd, buffer + total_read, len - total_read); + if ((tlsp) && (nread == TLS_WANT_POLLIN || nread == TLS_WANT_POLLOUT)) continue; + #else + nread = readsocket(fd, buffer + total_read, len - total_read); + #endif + if (nread == -1 && errno == EINTR) continue; + total_read += nread; + + if (nread <= 0) return nread; + if (total_read == len) break; + }; + + return total_read; +} +static SQCloudResult *internal_socket_read (SQCloudConnection *connection, bool mainfd) { int fd = (mainfd) ? connection->fd : connection->pubsubfd; - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS struct tls *tls = (mainfd) ? connection->tls_context : connection->tls_pubsub_context; + #else + void *tls = NULL; #endif - char *original = buffer; + ssize_t nread = 0; + uint32_t clen = 0; + uint32_t cstart = 0; + char header[64]; + int header_index = 0; + + char *buffer = NULL; + char static_buffer[4096]; + + // read the buffer one character at a time until a space is encountered + // see https://github.com/sqlitecloud/sdk/blob/master/PROTOCOL.md for more details about the protocol + // after this loop we can know the buffer type and len while (1) { - #ifndef SQLITECLOUD_DISABLE_TSL - ssize_t nread = (tls) ? tls_read(tls, buffer, blen) : readsocket(fd, buffer, blen); - if ((tls) && (nread == TLS_WANT_POLLIN || nread == TLS_WANT_POLLOUT)) continue; - #else - ssize_t nread = readsocket(fd, buffer, blen); - #endif - - if (nread < 0) { - const char *msg = ""; - #ifndef SQLITECLOUD_DISABLE_TSL - if (tls) msg = tls_error(tls); - #endif - - internal_set_error(connection, INTERNAL_ERRCODE_NETWORK, "An error occurred while reading data: %s (%s).", strerror(errno), msg); - goto abort_read; - } - - if (nread == 0) { - const char *msg = ""; - #ifndef SQLITECLOUD_DISABLE_TSL - if (tls) msg = tls_error(tls); - #endif - - internal_set_error(connection, INTERNAL_ERRCODE_NETWORK, "Unexpected EOF found while reading data: %s (%s).", strerror(errno), msg); - goto abort_read; + nread = internal_socket_read_nbytes(fd, tls, &header[header_index], 1); + if (nread <= 0) goto abort_read; + if (header[header_index] == ' ') break; + ++header_index; + + // check for malformed header + if (header_index >= sizeof(header)) { + internal_set_error(connection, INTERNAL_ERRCODE_NETWORK, "Bad protocol reply from server: unable to find buffer size (type was %c).", header[0]); + return NULL; } + } + + // parse len (if any) + int header_size = header_index + 1; // +1 because ++header_index; is after the break clause + if (internal_has_commandlen(header[0])) { + clen = internal_parse_number (&header[1], header_size-1, &cstart); - tread += (uint32_t)nread; - blen -= (uint32_t)nread; - buffer += nread; - - if (internal_has_commandlen(original[0])) { - // parse buffer looking for command length - if (clen == 0) clen = internal_parse_number (&original[1], tread-1, &cstart); - - // check special zero-length value - if (clen == 0) { - if (!internal_canbe_zerolength(original[0])) continue; - } - - // check if read is complete - // clen is the lenght parsed in the buffer - // cstart is the index of the first space - // +1 because we skipped the first character in the internal_parse_number function - if (clen + cstart + 1 != tread) { - // check buffer allocation and continue reading - if (clen + cstart - tread > blen) { - char *clone = mem_alloc(clen + cstart + 1); - if (!clone) { - internal_set_error(connection, INTERNAL_ERRCODE_MEMORY, "Unable to allocate memory: %d.", clen + cstart + 1); - goto abort_read; - } - memcpy(clone, original, tread); - buffer = original = clone; - blen = (clen + cstart + 1) - tread; - buffer += tread; - } - continue; + // check special zero-length value + if (clen == 0) { + if (internal_canbe_zerolength(header[0])) { + // it is perfectly legit to have a zero-bytes string or blob + return internal_parse_buffer(connection, header, header_size, 0, true, false); + } else { + // we parsed a zero-length header but we command does not allow that value, so return an error + internal_set_error(connection, INTERNAL_ERRCODE_NETWORK, "Bad protocol reply from server: the type %c cannot have a zero length buffer.", header[0]); + return NULL; } - } else { - // it is a command with no explicit len - // so make sure that the final character is a space - if (original[tread-1] != ' ') continue; } - - // command is complete so parse it - return internal_parse_buffer(connection, original, tread, (clen) ? cstart : 0, (original == header), false); + } else { + // command does not have an explicit len so the header can be safely processed + return internal_parse_buffer(connection, header, header_size, (clen) ? cstart : 0, true, false); + } + + // header correctly parsed and len is greater than zero, check if allocate a buffer or use a static one + // the static buffer optimization was added because of the +2 OK messages + size_t blen = clen + header_size; + buffer = (blen <= sizeof(static_buffer)) ? static_buffer : mem_alloc(blen); + if (!buffer) { + internal_set_error(connection, INTERNAL_ERRCODE_MEMORY, "Unable to allocate memory: %d.", blen); + return NULL; } -abort_read: - if (original != (char *)&header) mem_free(original); + // copy header back to buffer + memcpy(buffer, header, header_size); + + // read the remaing part of the command + nread = internal_socket_read_nbytes(fd, tls, &buffer[header_size], clen); + if (nread <= 0) goto abort_read; + + // command is complete so parse it + return internal_parse_buffer(connection, buffer, (uint32_t)blen, (clen) ? cstart : 0, (buffer == static_buffer), false); + +abort_read: { + const char *msg = ""; + const char *format = (nread == 0) ? "Unexpected EOF found while reading data: %s (%s)." : "An error occurred while reading data: %s (%s)."; + #ifndef SQLITECLOUD_DISABLE_TLS + if (tls) msg = tls_error(tls); + #endif + internal_set_error(connection, INTERNAL_ERRCODE_NETWORK, format, strerror(errno), msg); + } + + if (buffer && buffer != static_buffer) mem_free(buffer); return NULL; } -static bool internal_socket_raw_write (SQCloudConnection *connection, const char *buffer) { +static bool internal_socket_raw_write (SQCloudConnection *connection, const char *buffer, size_t len) { // this function is used only to debug possible security issues int fd = connection->fd; - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS struct tls *tls = connection->tls_context; #endif - size_t len = strlen(buffer); size_t written = 0; while (len > 0) { - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS ssize_t nwrote = (tls) ? tls_write(tls, buffer, len) : writesocket(fd, buffer, len); if ((tls) && (nwrote == TLS_WANT_POLLIN || nwrote == TLS_WANT_POLLOUT)) continue; #else @@ -1443,7 +1549,7 @@ static bool internal_socket_raw_write (SQCloudConnection *connection, const char if (nwrote < 0) { const char *msg = ""; - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS if (tls) msg = tls_error(tls); #endif return internal_set_error(connection, INTERNAL_ERRCODE_NETWORK, "An error occurred while writing data: %s (%s).", strerror(errno), msg); @@ -1461,20 +1567,34 @@ static bool internal_socket_raw_write (SQCloudConnection *connection, const char static bool internal_socket_write (SQCloudConnection *connection, const char *buffer, size_t len, bool mainfd, bool compute_header) { int fd = (mainfd) ? connection->fd : connection->pubsubfd; - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS struct tls *tls = (mainfd) ? connection->tls_context : connection->tls_pubsub_context; #endif - size_t written = 0; + + // optimization tip + // instead of writing twice to the socket (one for the header and one for the data) + // try to pack everything inside the same buffer, which is generally faster + // -12 is to reserve enough space for the header + char blocal[4096]; + if (compute_header && !connection->isblob && (len < sizeof(blocal)-12)) { + size_t len_local = snprintf(blocal, sizeof(blocal), "+%zu %s", len, buffer); + if (len_local < sizeof(blocal)) { + len = len_local; + buffer = blocal; + compute_header = false; + } + } // write header + size_t written = 0; if (compute_header) { char header[32]; char *p = header; int hlen = snprintf(header, sizeof(header), "%c%zu ", (connection->isblob) ? CMD_BLOB : CMD_STRING, len); int len1 = hlen; while (len1) { - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS ssize_t nwrote = (tls) ? tls_write(tls, p, len1) : writesocket(fd, p, len1); if ((tls) && (nwrote == TLS_WANT_POLLIN || nwrote == TLS_WANT_POLLOUT)) continue; #else @@ -1483,7 +1603,7 @@ static bool internal_socket_write (SQCloudConnection *connection, const char *bu if ((nwrote < 0) || (nwrote == 0 && written != hlen)) { const char *msg = ""; - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS if (tls) msg = tls_error(tls); #endif return internal_set_error(connection, INTERNAL_ERRCODE_NETWORK, "An error occurred while writing header data: %s (%s).", strerror(errno), msg); @@ -1498,7 +1618,7 @@ static bool internal_socket_write (SQCloudConnection *connection, const char *bu // write buffer written = 0; while (len > 0) { - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS ssize_t nwrote = (tls) ? tls_write(tls, buffer, len) : writesocket(fd, buffer, len); if ((tls) && (nwrote == TLS_WANT_POLLIN || nwrote == TLS_WANT_POLLOUT)) continue; #else @@ -1507,7 +1627,7 @@ static bool internal_socket_write (SQCloudConnection *connection, const char *bu if (nwrote < 0) { const char *msg = ""; - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS if (tls) msg = tls_error(tls); #endif return internal_set_error(connection, INTERNAL_ERRCODE_NETWORK, "An error occurred while writing data: %s (%s).", strerror(errno), msg); @@ -1545,20 +1665,29 @@ static bool internal_connect_apply_config (SQCloudConnection *connection, SQClou char buffer[2048]; int len = 0; + // non-linearizable option must be executed first + if (config->non_linearizable) { + len += snprintf(&buffer[len], sizeof(buffer) - len, "SET CLIENT KEY NONLINEARIZABLE TO 1;"); + } + if (config->username && config->password && strlen(config->username) && strlen(config->password)) { char *command = config->password_hashed ? "HASH" : "PASSWORD"; len += snprintf(&buffer[len], sizeof(buffer) - len, "AUTH USER %s %s %s;", config->username, command, config->password); } + if (config->api_key && strlen(config->api_key)) { + len += snprintf(&buffer[len], sizeof(buffer) - len, "AUTH APIKEY %s;", config->api_key); + } + + if (config->token && strlen(config->token)) { + len += snprintf(&buffer[len], sizeof(buffer) - len, "AUTH TOKEN %s;", config->token); + } + if (config->database && strlen(config->database)) { if (config->db_create && !config->db_memory) len += snprintf(&buffer[len], sizeof(buffer) - len, "CREATE DATABASE %s IF NOT EXISTS;", config->database); len += snprintf(&buffer[len], sizeof(buffer) - len, "USE DATABASE %s;", config->database); } - if (config->sqlite_mode) { - len += snprintf(&buffer[len], sizeof(buffer) - len, "SET CLIENT KEY SQLITE TO 1;"); - } - if (config->compression) { len += snprintf(&buffer[len], sizeof(buffer) - len, "SET CLIENT KEY COMPRESSION TO 1;"); } @@ -1567,10 +1696,6 @@ static bool internal_connect_apply_config (SQCloudConnection *connection, SQClou len += snprintf(&buffer[len], sizeof(buffer) - len, "SET CLIENT KEY ZEROTEXT TO 1;"); } - if (config->nonlinearizable) { - len += snprintf(&buffer[len], sizeof(buffer) - len, "SET CLIENT KEY NONLINEARIZABLE TO 1;"); - } - if (config->no_blob) { len += snprintf(&buffer[len], sizeof(buffer) - len, "SET CLIENT KEY NOBLOB TO 1;"); } @@ -1606,8 +1731,12 @@ static bool internal_connect (SQCloudConnection *connection, const char *hostnam // ipv6 code from https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_72/rzab6/xip6client.htm memset(&hints, 0, sizeof(hints)); - hints.ai_family = (config) ? config->family : AF_INET; + hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; + if (config && config->family) { + if (config->family == SQCLOUD_IPv6) hints.ai_family = AF_INET6; + if (config->family == SQCLOUD_IPANY) hints.ai_family = AF_UNSPEC; + } // get the address information for the server using getaddrinfo() char port_string[256]; @@ -1631,8 +1760,12 @@ static bool internal_connect (SQCloudConnection *connection, const char *hostnam // set socket options int len = 1; setsockopt(sock_current, SOL_SOCKET, SO_KEEPALIVE, (const char *) &len, sizeof(len)); + + // disable Nagle algorithm because we want our writes to be sent ASAP + // https://brooker.co.za/blog/2024/05/09/nagle.html len = 1; setsockopt(sock_current, IPPROTO_TCP, TCP_NODELAY, (const char *) &len, sizeof(len)); + #ifdef SO_NOSIGPIPE len = 1; setsockopt(sock_current, SOL_SOCKET, SO_NOSIGPIPE, (const char *) &len, sizeof(len)); @@ -1755,7 +1888,7 @@ static bool internal_connect (SQCloudConnection *connection, const char *hostnam connection->fd = sockfd; connection->port = port; connection->hostname = mem_string_dup(hostname); - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS if (config && !config->insecure) { rc = tls_connect_socket(connection->tls_context, sockfd, hostname); if (rc < 0) printf("Error on tls_connect_socket: %s\n", tls_error(connection->tls_context)); @@ -1763,7 +1896,7 @@ static bool internal_connect (SQCloudConnection *connection, const char *hostnam #endif } else { connection->pubsubfd = sockfd; - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS if (config && !config->insecure) { rc = tls_connect_socket(connection->tls_pubsub_context, sockfd, hostname); if (rc < 0) printf("Error on tls_connect_socket\n"); @@ -1928,6 +2061,7 @@ bool internal_upload_database (SQCloudConnection *connection, const char *dbname uint32_t blen = 0; int64_t nprogress = 0; + bool result = false; do { // execute callback to read buffer blen = SQCLOUD_DEFAULT_UPLOAD_SIZE; @@ -1935,17 +2069,21 @@ bool internal_upload_database (SQCloudConnection *connection, const char *dbname if (rc != 0) { SQCloudResult *res = SQCloudExec(connection, "UPLOAD ABORT"); SQCloudResultFree(res); - return false; + goto cleanup; } // send BLOB - if (SQCloudSendBLOB(connection, buffer, blen) == false) return false; + if (internal_send_blob(connection, buffer, blen) == false) goto cleanup; // update progress nprogress += blen; } while (blen > 0); - return true; + result = true; + +cleanup: + mem_free(buffer); + return result; } // n is the total number of items in the array @@ -1992,7 +2130,9 @@ void internal_free_config (SQCloudConfig *config) { if (config->username) mem_free((void *)config->username); if (config->password) mem_free((void *)config->password); if (config->database) mem_free((void *)config->database); - #ifndef SQLITECLOUD_DISABLE_TSL + if (config->api_key) mem_free((void *)config->database); + if (config->token) mem_free((void *)config->token); + #ifndef SQLITECLOUD_DISABLE_TLS if (config->tls_root_certificate) mem_free((void *)config->tls_root_certificate); if (config->tls_certificate) mem_free((void *)config->tls_certificate); if (config->tls_certificate_key) mem_free((void *)config->tls_certificate_key); @@ -2197,11 +2337,7 @@ bool _reserved5 (SQCloudResult *res) { bool _reserved6 (SQCloudConnection *connection, const char *buffer) { internal_clear_error(connection); - return internal_socket_raw_write(connection, buffer); -} - -SQCloudResult *_reserved7 (SQCloudConnection *connection) { - return internal_socket_read(connection, true); + return internal_socket_raw_write(connection, buffer, strlen(buffer)); } bool _reserved8 (SQCloudConnection *connection, const char *dbname, const char *key, uint64_t snapshotid, bool isinternaldb, void *xdata, int64_t dbsize, int (*xCallback)(void *xdata, void *buffer, uint32_t *blen, int64_t ntot, int64_t nprogress)) { @@ -2253,7 +2389,7 @@ char *_reserved11 (char *buffer, uint32_t blen, uint32_t index, uint32_t *len, u if (type && buffer) *type = buffer[0]; if (len) *len = tlen; if (cellsize) *cellsize = tcellsize; - if (pos) *pos = (uint32_t)(value - p); + if (pos) *pos = (uint32_t)((value ? value : buffer) - p); return value; } @@ -2276,7 +2412,7 @@ bool _reserved13 (SQCloudConnection *connection, const char *dbname, void *xdata // prepare command to execute char buffer[512]; - snprintf(buffer, sizeof(buffer), "DOWNLOAD DATABASE %s%s", dbname, (ifexists) ? " IF EXISTS" : ""); + snprintf(buffer, sizeof(buffer), "DOWNLOAD DATABASE '%s' %s", dbname, (ifexists) ? "IF EXISTS" : ""); // execute command on server side SQCloudResult *res = SQCloudExec(connection, buffer); @@ -2390,7 +2526,7 @@ SQCloudConnection *SQCloudConnectWithString (const char *s, SQCloudConfig *pconf if (rc > 0) { config->database = mem_string_dup(database); - config->db_memory = (strcmp(database, ":memory:") == 0 || strcmp(database, ":temp:") == 0); + config->db_memory = (strcasecmp(database, ":memory:") == 0 || strcasecmp(database, ":temp:") == 0); } } @@ -2407,10 +2543,6 @@ SQCloudConnection *SQCloudConnectWithString (const char *s, SQCloudConfig *pconf int compression = (int)strtol(value, NULL, 0); config->compression = (compression > 0) ? true : false; } - else if (strcasecmp(key, "sqlite") == 0) { - int sqlite_mode = (int)strtol(value, NULL, 0); - config->sqlite_mode = (sqlite_mode > 0) ? true : false; - } else if (strcasecmp(key, "zerotext") == 0) { int zero_text = (int)strtol(value, NULL, 0); config->zero_text = (zero_text > 0) ? true : false; @@ -2423,11 +2555,19 @@ SQCloudConnection *SQCloudConnectWithString (const char *s, SQCloudConfig *pconf int db_create = (int)strtol(value, NULL, 0); if (db_create) config->db_create = (db_create > 0) ? true : false; } - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS else if (strcasecmp(key, "insecure") == 0) { int insecure = (int)strtol(value, NULL, 0); config->insecure = (insecure > 0) ? true : false; } + else if (strcasecmp(key, "no_verify_certificate") == 0) { + int no_verify_certificate = (int)strtol(value, NULL, 0); + config->no_verify_certificate = (no_verify_certificate > 0) ? true : false; + } + else if ((strcasecmp(key, "non_linearizable") == 0) || (strcasecmp(key, "nonlinearizable") == 0)) { + int dvalue = (int)strtol(value, NULL, 0); + config->non_linearizable = (dvalue > 0) ? true : false; + } else if (strcasecmp(key, "root_certificate") == 0) { config->tls_root_certificate = mem_string_dup(value); } @@ -2443,13 +2583,22 @@ SQCloudConnection *SQCloudConnectWithString (const char *s, SQCloudConfig *pconf config->no_blob = (no_blob > 0) ? true : false; } else if (strcasecmp(key, "maxdata") == 0) { - config->max_data = (int)strtol(value, NULL, 0); + int dvalue = (int)strtol(value, NULL, 0); + if (dvalue >= 0) config->max_data = dvalue; } else if (strcasecmp(key, "maxrows") == 0) { - config->max_rows = (int)strtol(value, NULL, 0); + int dvalue = (int)strtol(value, NULL, 0); + if (dvalue >= 0) config->max_rows = dvalue; } else if (strcasecmp(key, "maxrowset") == 0) { - config->max_rowset = (int)strtol(value, NULL, 0); + int dvalue = (int)strtol(value, NULL, 0); + if (dvalue >= 0) config->max_rowset = dvalue; + } + else if (strcasecmp(key, "apikey") == 0) { + config->api_key = mem_string_dup(value); + } + else if (strcasecmp(key, "token") == 0) { + config->token = mem_string_dup(value); } n += rc; } @@ -2458,9 +2607,8 @@ SQCloudConnection *SQCloudConnectWithString (const char *s, SQCloudConfig *pconf if (pconfig) { if (pconfig->timeout) config->timeout = pconfig->timeout; if (pconfig->compression) config->compression = pconfig->compression; - if (pconfig->sqlite_mode) config->sqlite_mode = pconfig->sqlite_mode; if (pconfig->zero_text) config->zero_text = pconfig->zero_text; - if (pconfig->nonlinearizable) config->nonlinearizable = pconfig->nonlinearizable; + if (pconfig->non_linearizable) config->non_linearizable = pconfig->non_linearizable; if (pconfig->no_blob) config->no_blob = pconfig->no_blob; if (pconfig->db_create) config->db_create = pconfig->db_create; if (pconfig->max_data) config->max_data = pconfig->max_data; @@ -2471,6 +2619,14 @@ SQCloudConnection *SQCloudConnectWithString (const char *s, SQCloudConfig *pconf if (config->database) mem_free((void *)config->database); config->database = mem_string_dup(":memory:"); } + if (pconfig->api_key) { + if (config->api_key) mem_free((void *)config->api_key); + config->api_key = mem_string_dup(pconfig->api_key); + } + if (pconfig->token) { + if (config->token) mem_free((void *)config->token); + config->token = mem_string_dup(pconfig->token); + } } SQCloudConnection *connection = SQCloudConnect(hostname, port, config); @@ -2488,6 +2644,17 @@ SQCloudResult *SQCloudExec (SQCloudConnection *connection, const char *command) return internal_run_command(connection, command, strlen(command), true); } +SQCloudResult *SQCloudExecRaw (SQCloudConnection *connection, const char *command, size_t len) { + internal_clear_error(connection); + + TIME_GET(tstart); + if (!internal_socket_raw_write(connection, command, len)) return NULL; + SQCloudResult *result = internal_socket_read(connection, true); + TIME_GET(tend); + if (result) result->time = TIME_VAL(tstart, tend); + return result; +} + SQCloudResult *SQCloudExecArray (SQCloudConnection *connection, const char *command, const char **values, uint32_t len[], SQCLOUD_VALUE_TYPE types[], uint32_t n) { if (!command) return NULL; if (n == 0) return SQCloudExec(connection, command); @@ -2580,19 +2747,11 @@ SQCloudResult *SQCloudExecArray (SQCloudConnection *connection, const char *comm return result; } -SQCloudResult *SQCloudRead (SQCloudConnection *connection) { - return internal_socket_read(connection, true); -} - -bool SQCloudSendBLOB (SQCloudConnection *connection, void *buffer, uint32_t blen) { - return internal_send_blob(connection, buffer, blen); -} - void SQCloudDisconnect (SQCloudConnection *connection) { if (!connection) return; // free TLS - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS if (connection->tls_context) { tls_close(connection->tls_context); tls_free(connection->tls_context); @@ -2644,8 +2803,18 @@ SQCloudResult *SQCloudSetPubSubOnly (SQCloudConnection *connection) { return internal_run_command(connection, command, strlen(command), true); } -char *SQCloudUUID (SQCloudConnection *connection) { - return connection->uuid; +const char *SQCloudUUID (SQCloudConnection *connection) { + if (!connection->uuid) { + SQCloudResult *result = SQCloudExec(connection, "GET CLIENT KEY UUID"); + if (SQCloudResultType(result) == RESULT_STRING) connection->uuid = mem_string_ndup(SQCloudResultBuffer(result), SQCloudResultLen(result)); + SQCloudResultFree(result); + } + + return (const char *)connection->uuid; +} + +SQCloudConfig *SQCloudGetConfig (SQCloudConnection *connection) { + return connection->_config; } // MARK: - ERROR - @@ -2736,6 +2905,18 @@ double SQCloudResultDouble (SQCloudResult *result) { return (double)strtod(buffer, NULL); } +float SQCloudResultFloat (SQCloudResult *result) { + if ((!result) || (result->tag != RESULT_FLOAT)) return 0.0; + + char *buffer = result->buffer; + buffer[result->blen] = 0; + return (float)strtof(buffer, NULL); +} + +double SQCloudResultTime (SQCloudResult *result) { + return result->time; +} + void SQCloudResultFree (SQCloudResult *result) { if (!result || (result == &SQCloudResultOK) || (result == &SQCloudResultNULL)) return; @@ -2752,11 +2933,14 @@ void SQCloudResultFree (SQCloudResult *result) { if (result->tblname) mem_free(result->tblname); if (result->origname) mem_free(result->origname); - if (result->ischunk && !result->externalbuffer) { + if (result->ischunk) { + // each buffer has its own externalbuffer flag, it depends on whether the original + // buffer was external or not and whether it was reallocated (in case of compression) or not for (uint32_t i = 0; ibcount; ++i) { - if (result->buffers[i]) mem_free(result->buffers[i]); + if (result->buffers[i] && !result->bext[i]) mem_free(result->buffers[i]); } mem_free(result->buffers); + mem_free(result->bext); mem_free(result->blens); mem_free(result->nheads); } @@ -2855,6 +3039,33 @@ char *SQCloudRowsetColumnOrigName (SQCloudResult *result, uint32_t col, uint32_t return internal_get_rowset_header(result, result->origname, col, len); } +uint32_t SQCloudRowSetColumnNotNULL (SQCloudResult *result, uint32_t col) { + return internal_get_rowset_header_int(result, result->notnull, col); +} + +uint32_t SQCloudRowSetColumnPrimaryKey (SQCloudResult *result, uint32_t col) { + return internal_get_rowset_header_int(result, result->prikey, col); +} + +uint32_t SQCloudRowSetColumnAutoIncrement (SQCloudResult *result, uint32_t col) { + return internal_get_rowset_header_int(result, result->autoinc, col); +} + +bool SQCloudRowsetCanWrite (SQCloudResult *result) { + // check if the rowset is not a JOIN (must have the same table) + char *keytable = result->tblname[0]; + for (int i=1; incols; ++i) { + if (strcmp(keytable, result->tblname[i]) != 0) return false; + } + + // check if contains at least a primary key + for (int i=0; incols; ++i) { + if (result->prikey[i] == 1) return true; + } + + return false; +} + uint32_t SQCloudRowsetRows (SQCloudResult *result) { if (!SQCloudRowsetSanityCheck(result, 0, 0)) return 0; return result->nrows; @@ -3228,6 +3439,7 @@ SQCLOUD_RESULT_TYPE SQCloudVMStep (SQCloudVM *vm) { if (type == RESULT_ARRAY) { SQCloudVMSetResult(vm, NULL); SQCloudArrayResultDecode(vm, result); + SQCloudResultFree(result); return RESULT_OK; } @@ -3272,7 +3484,7 @@ bool SQCloudVMIsReadOnly (SQCloudVM *vm) { return vm->isreadonly; } -bool SQCloudVMIsExplain (SQCloudVM *vm) { +int SQCloudVMIsExplain (SQCloudVM *vm) { return vm->isexplain; } @@ -3280,6 +3492,51 @@ int SQCloudVMBindParameterCount (SQCloudVM *vm) { return vm->nparams; } +int SQCloudVMBindParameterIndex (SQCloudVM *vm, const char *name) { + // VM PARAMETER INDEX + + char sql[512]; + snprintf(sql, sizeof(sql), "VM PARAMETER %d INDEX ?;", vm->index); + + const char *r[1] = {name}; + uint32_t rlen[1] = {(uint32_t)strlen(name)}; + SQCLOUD_VALUE_TYPE types[1] = {VALUE_TEXT}; + + SQCloudResult *result = SQCloudExecArray(vm->connection, sql, r, rlen, types, 1); + if (SQCloudResultType(result) == RESULT_ERROR) { + SQCloudVMSetError(vm); + SQCloudResultFree(result); + return 0; + } + + int index = SQCloudResultInt32(result); + SQCloudResultFree(result); + return index; +} + +const char *SQCloudVMBindParameterName (SQCloudVM *vm, int index) { + // VM PARAMETER NAME + + char sql[512]; + snprintf(sql, sizeof(sql), "VM PARAMETER %d NAME %d;", vm->index, index); + + SQCloudResult *result = SQCloudExec(vm->connection, sql); + if (SQCloudResultType(result) == RESULT_ERROR) { + SQCloudVMSetError(vm); + SQCloudResultFree(result); + return NULL; + } + + const char *name = NULL; + if (SQCloudResultBuffer(result) != NULL) { + name = mem_string_dup(SQCloudResultBuffer(result)); + } + + SQCloudResultFree(result); + return name; +} + + int SQCloudVMColumnCount (SQCloudVM *vm) { return vm->ncolumns; } @@ -3473,8 +3730,10 @@ bool SQCloudBlobReOpen (SQCloudBlob *blob, int64_t rowid) { if (SQCloudResultType(result) == RESULT_ERROR) { blob->rc = SQCloudErrorCode(blob->connection); } - SQCloudResultFree(result); + + // make sure to reset bytes counter + blob->bytes = -1; return (blob->rc == 0); } diff --git a/C/sqcloud.h b/C/sqcloud.h index ccf56873..015ab8ee 100644 --- a/C/sqcloud.h +++ b/C/sqcloud.h @@ -15,15 +15,15 @@ extern "C" { #endif -#define SQCLOUD_SDK_VERSION "0.9.0" -#define SQCLOUD_SDK_VERSION_NUM 0x000900 +#define SQCLOUD_SDK_VERSION "0.9.9" +#define SQCLOUD_SDK_VERSION_NUM 0x000909 #define SQCLOUD_DEFAULT_PORT 8860 #define SQCLOUD_DEFAULT_TIMEOUT 12 #define SQCLOUD_DEFAULT_UPLOAD_SIZE 512*1024 -#define SQCLOUD_IPany 0 -#define SQCLOUD_IPv4 2 -#define SQCLOUD_IPv6 30 +#define SQCLOUD_IPv4 0 +#define SQCLOUD_IPv6 1 +#define SQCLOUD_IPANY 2 #ifndef BITCHECK #define BITCHECK(byte,nbit) ((byte) & (1<<(nbit))) @@ -47,6 +47,15 @@ extern "C" { #define CMD_COMMAND '^' #define CMD_RECONNECT '@' #define CMD_ARRAY '=' +#define CMD_ASYNC_STRING '>' +#define CMD_ASYNC_ARRAY '<' + +typedef enum { + ROWSET_TYPE_BASIC = 1, + ROWSET_TYPE_METADATA_v1 = 2, + ROWSET_TYPE_HEADER_ONLY = 3, + ROWSET_TYPE_DATA_ONLY = 4 +} SQCLOUD_ROWSET_TYPE; // MARK: - @@ -60,29 +69,31 @@ typedef void (*SQCloudPubSubCB) (SQCloudConnection *connection, SQCl typedef int (*config_cb) (char *buffer, int len, void *data); typedef int64_t (*SQCloudBackupOnDataCB) (SQCloudBackup *backup, const char *data, uint32_t len, int page_size, int page_counter); -// configuration struct to be passed to the connect function (currently unused) +// configuration struct to be passed to the connect function typedef struct SQCloudConfigStruct { - const char *username; - const char *password; - const char *database; - int timeout; - int family; // can be: SQCLOUD_IPv4, SQCLOUD_IPv6 or SQCLOUD_IPany + const char *username; // connection username + const char *password; // connection password + const char *database; // database to use during connection + const char *api_key; // APIKEY + const char *token; // token + int timeout; // connection timeout parameter + int family; // can be: SQCLOUD_IPv4, SQCLOUD_IPv6 or SQCLOUD_IPANY bool compression; // compression flag - bool sqlite_mode; // special sqlite compatibility mode bool zero_text; // flag to tell the server to zero-terminate strings bool password_hashed; // private flag - bool nonlinearizable; // flag to request for immediate responses from the server node without waiting for linerizability guarantees + bool non_linearizable; // flag to request for immediate responses from the server node without waiting for linerizability guarantees bool db_memory; // flag to force the database to be in-memory bool no_blob; // flag to tell the server to not send BLOB columns bool db_create; // flag to force the creation of the database (if it does not exist) int max_data; // value to tell the server to not send columns with more than max_data bytes int max_rows; // value to control rowset chunks based on the number of rows int max_rowset; // value to control the maximum allowed size for a rowset - #ifndef SQLITECLOUD_DISABLE_TSL + #ifndef SQLITECLOUD_DISABLE_TLS const char *tls_root_certificate; const char *tls_certificate; const char *tls_certificate_key; bool insecure; // flag to disable TLS + bool no_verify_certificate; // flag to accept invalid TLS certificates #endif config_cb callback; // reserved callback for internal usage void *data; // reserved callback data parameter @@ -115,14 +126,6 @@ typedef enum { VALUE_NULL = 5 } SQCLOUD_VALUE_TYPE; -typedef enum { - SQCLOUD_ROWSET_FLAG_STANDARD = 0, // rowset contains standard header and data - SQCLOUD_ROWSET_FLAG_METACOLS = 1, // rowset contains additional columns metadata - SQCLOUD_ROWSET_FLAG_HEADONLY = 2, // rowset is header only - SQCLOUD_ROWSET_FLAG_DATAONLY = 3, // rowset is data only - SQCLOUD_ROWSET_FLAG_METAVM = 4 // rowset contains VM metadata info -} SQCLOUD_ROWSET_FLAG; - typedef enum { ARRAY_TYPE_SQLITE_EXEC = 10, // used in SQLITE_MODE only when a write statement is executed (instead of the OK reply) ARRAY_TYPE_DB_STATUS = 11, @@ -151,7 +154,8 @@ typedef enum { INTERNAL_ERRCODE_MEMORY = 100004, INTERNAL_ERRCODE_NETWORK = 100005, INTERNAL_ERRCODE_FORMAT = 100006, - INTERNAL_ERRCODE_INDEX = 100007 + INTERNAL_ERRCODE_INDEX = 100007, + INTERNAL_ERRCODE_SOCKCLOSED = 100008, } SQCLOUD_INTERNAL_ERRCODE; // from SQLiteCloud @@ -169,10 +173,12 @@ typedef enum { SQCloudConnection *SQCloudConnect (const char *hostname, int port, SQCloudConfig *config); SQCloudConnection *SQCloudConnectWithString (const char *s, SQCloudConfig *config); SQCloudResult *SQCloudExec (SQCloudConnection *connection, const char *command); -SQCloudResult *SQCloudRead (SQCloudConnection *connection); -char *SQCloudUUID (SQCloudConnection *connection); -bool SQCloudSendBLOB (SQCloudConnection *connection, void *buffer, uint32_t blen); +SQCloudResult *SQCloudExecRaw (SQCloudConnection *connection, const char *command, size_t len); +SQCloudConfig *SQCloudGetConfig (SQCloudConnection *connection); +const char *SQCloudUUID (SQCloudConnection *connection); void SQCloudDisconnect (SQCloudConnection *connection); + +// MARK: - Pub/Sub - void SQCloudSetPubSubCallback (SQCloudConnection *connection, SQCloudPubSubCB callback, void *data); SQCloudResult *SQCloudSetPubSubOnly (SQCloudConnection *connection); @@ -194,6 +200,8 @@ char *SQCloudResultBuffer (SQCloudResult *result); int32_t SQCloudResultInt32 (SQCloudResult *result); int64_t SQCloudResultInt64 (SQCloudResult *result); double SQCloudResultDouble (SQCloudResult *result); +float SQCloudResultFloat (SQCloudResult *result); +double SQCloudResultTime (SQCloudResult *result); void SQCloudResultFree (SQCloudResult *result); bool SQCloudResultIsOK (SQCloudResult *result); bool SQCloudResultIsError (SQCloudResult *result); @@ -207,6 +215,9 @@ char *SQCloudRowsetColumnDeclType (SQCloudResult *result, uint32_t col, uint32_t char *SQCloudRowsetColumnDBName (SQCloudResult *result, uint32_t col, uint32_t *len); char *SQCloudRowsetColumnTblName (SQCloudResult *result, uint32_t col, uint32_t *len); char *SQCloudRowsetColumnOrigName (SQCloudResult *result, uint32_t col, uint32_t *len); +uint32_t SQCloudRowSetColumnNotNULL (SQCloudResult *result, uint32_t col); +uint32_t SQCloudRowSetColumnPrimaryKey (SQCloudResult *result, uint32_t col); +uint32_t SQCloudRowSetColumnAutoIncrement (SQCloudResult *result, uint32_t col); uint32_t SQCloudRowsetRows (SQCloudResult *result); uint32_t SQCloudRowsetCols (SQCloudResult *result); uint32_t SQCloudRowsetMaxLen (SQCloudResult *result); @@ -218,6 +229,7 @@ float SQCloudRowsetFloatValue (SQCloudResult *result, uint32_t row, uint32_t col double SQCloudRowsetDoubleValue (SQCloudResult *result, uint32_t row, uint32_t col); void SQCloudRowsetDump (SQCloudResult *result, uint32_t maxline, bool quiet); bool SQCloudRowsetCompare (SQCloudResult *result1, SQCloudResult *result2); +bool SQCloudRowsetCanWrite (SQCloudResult *result); // MARK: - Array - SQCloudResult *SQCloudExecArray (SQCloudConnection *connection, const char *command, const char **values, uint32_t len[], SQCLOUD_VALUE_TYPE types[], uint32_t n); @@ -244,9 +256,11 @@ const char *SQCloudVMErrorMsg (SQCloudVM *vm); int SQCloudVMErrorCode (SQCloudVM *vm); int SQCloudVMIndex (SQCloudVM *vm); bool SQCloudVMIsReadOnly (SQCloudVM *vm); -bool SQCloudVMIsExplain (SQCloudVM *vm); +int SQCloudVMIsExplain (SQCloudVM *vm); bool SQCloudVMIsFinalized (SQCloudVM *vm); int SQCloudVMBindParameterCount (SQCloudVM *vm); +int SQCloudVMBindParameterIndex (SQCloudVM *vm, const char *name); +const char *SQCloudVMBindParameterName (SQCloudVM *vm, int index); int SQCloudVMColumnCount (SQCloudVM *vm); bool SQCloudVMBindDouble (SQCloudVM *vm, int index, double value); bool SQCloudVMBindInt (SQCloudVM *vm, int index, int value); @@ -261,10 +275,10 @@ double SQCloudVMColumnDouble (SQCloudVM *vm, int index); int SQCloudVMColumnInt32 (SQCloudVM *vm, int index); int64_t SQCloudVMColumnInt64 (SQCloudVM *vm, int index); int64_t SQCloudVMColumnLen (SQCloudVM *vm, int index); +SQCLOUD_VALUE_TYPE SQCloudVMColumnType (SQCloudVM *vm, int index); int64_t SQCloudVMLastRowID (SQCloudVM *vm); int64_t SQCloudVMChanges (SQCloudVM *vm); int64_t SQCloudVMTotalChanges (SQCloudVM *vm); -SQCLOUD_VALUE_TYPE SQCloudVMColumnType (SQCloudVM *vm, int index); // MARK: - BLOB - SQCloudBlob *SQCloudBlobOpen (SQCloudConnection *connection, const char *dbname, const char *tablename, const char *colname, int64_t rowid, bool wrflag); @@ -284,6 +298,9 @@ void *SQCloudBackupSetData (SQCloudBackup *backup, void *data); void *SQCloudBackupData (SQCloudBackup *backup); SQCloudConnection *SQCloudBackupConnection (SQCloudBackup *backup); +// MARK: - Reserved - + + #ifdef __cplusplus } #endif diff --git a/C/test/test.xcodeproj/project.pbxproj b/C/test/test.xcodeproj/project.pbxproj deleted file mode 100644 index 5bb6714e..00000000 --- a/C/test/test.xcodeproj/project.pbxproj +++ /dev/null @@ -1,311 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 55; - objects = { - -/* Begin PBXBuildFile section */ - A9FC6DC228CC528B00689131 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = A9FC6DC128CC528B00689131 /* main.c */; }; - A9FC6DCC28CC52BC00689131 /* lz4.c in Sources */ = {isa = PBXBuildFile; fileRef = A9FC6DC928CC52BC00689131 /* lz4.c */; }; - A9FC6DCD28CC52BC00689131 /* sqcloud.c in Sources */ = {isa = PBXBuildFile; fileRef = A9FC6DCA28CC52BC00689131 /* sqcloud.c */; }; - A9FC6DCF28CC52CB00689131 /* libtls.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A9FC6DCE28CC52CB00689131 /* libtls.a */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - A9FC6DBC28CC528B00689131 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 1; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - A9FC6DBE28CC528B00689131 /* test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = test; sourceTree = BUILT_PRODUCTS_DIR; }; - A9FC6DC128CC528B00689131 /* main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = main.c; sourceTree = ""; }; - A9FC6DC828CC52BC00689131 /* sqcloud.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sqcloud.h; path = ../../sqcloud.h; sourceTree = ""; }; - A9FC6DC928CC52BC00689131 /* lz4.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = lz4.c; path = ../../lz4.c; sourceTree = ""; }; - A9FC6DCA28CC52BC00689131 /* sqcloud.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sqcloud.c; path = ../../sqcloud.c; sourceTree = ""; }; - A9FC6DCB28CC52BC00689131 /* lz4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lz4.h; path = ../../lz4.h; sourceTree = ""; }; - A9FC6DCE28CC52CB00689131 /* libtls.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtls.a; path = ../../SSL/macos_fat/libtls.a; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - A9FC6DBB28CC528B00689131 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - A9FC6DCF28CC52CB00689131 /* libtls.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - A9FC6DB528CC528B00689131 = { - isa = PBXGroup; - children = ( - A9FC6DC028CC528B00689131 /* test */, - A9FC6DBF28CC528B00689131 /* Products */, - ); - sourceTree = ""; - }; - A9FC6DBF28CC528B00689131 /* Products */ = { - isa = PBXGroup; - children = ( - A9FC6DBE28CC528B00689131 /* test */, - ); - name = Products; - sourceTree = ""; - }; - A9FC6DC028CC528B00689131 /* test */ = { - isa = PBXGroup; - children = ( - A9FC6DC128CC528B00689131 /* main.c */, - A9FC6DC928CC52BC00689131 /* lz4.c */, - A9FC6DCB28CC52BC00689131 /* lz4.h */, - A9FC6DCA28CC52BC00689131 /* sqcloud.c */, - A9FC6DC828CC52BC00689131 /* sqcloud.h */, - A9FC6DCE28CC52CB00689131 /* libtls.a */, - ); - path = test; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - A9FC6DBD28CC528B00689131 /* test */ = { - isa = PBXNativeTarget; - buildConfigurationList = A9FC6DC528CC528B00689131 /* Build configuration list for PBXNativeTarget "test" */; - buildPhases = ( - A9FC6DBA28CC528B00689131 /* Sources */, - A9FC6DBB28CC528B00689131 /* Frameworks */, - A9FC6DBC28CC528B00689131 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = test; - productName = test; - productReference = A9FC6DBE28CC528B00689131 /* test */; - productType = "com.apple.product-type.tool"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - A9FC6DB628CC528B00689131 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastUpgradeCheck = 1400; - TargetAttributes = { - A9FC6DBD28CC528B00689131 = { - CreatedOnToolsVersion = 13.4.1; - }; - }; - }; - buildConfigurationList = A9FC6DB928CC528B00689131 /* Build configuration list for PBXProject "test" */; - compatibilityVersion = "Xcode 13.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = A9FC6DB528CC528B00689131; - productRefGroup = A9FC6DBF28CC528B00689131 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - A9FC6DBD28CC528B00689131 /* test */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXSourcesBuildPhase section */ - A9FC6DBA28CC528B00689131 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - A9FC6DCC28CC52BC00689131 /* lz4.c in Sources */, - A9FC6DCD28CC52BC00689131 /* sqcloud.c in Sources */, - A9FC6DC228CC528B00689131 /* main.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - A9FC6DC328CC528B00689131 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES; - 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - 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; - MACOSX_DEPLOYMENT_TARGET = 12.3; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - }; - name = Debug; - }; - A9FC6DC428CC528B00689131 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES; - 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - 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; - MACOSX_DEPLOYMENT_TARGET = 12.3; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = macosx; - }; - name = Release; - }; - A9FC6DC628CC528B00689131 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - CODE_SIGN_STYLE = Automatic; - DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = 3ZH6236ET5; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../SSL/include\""; - LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../SSL/macos_fat\""; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - A9FC6DC728CC528B00689131 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - CODE_SIGN_STYLE = Manual; - DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = ""; - HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../SSL/include\""; - LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../SSL/macos_fat\""; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - A9FC6DB928CC528B00689131 /* Build configuration list for PBXProject "test" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - A9FC6DC328CC528B00689131 /* Debug */, - A9FC6DC428CC528B00689131 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - A9FC6DC528CC528B00689131 /* Build configuration list for PBXNativeTarget "test" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - A9FC6DC628CC528B00689131 /* Debug */, - A9FC6DC728CC528B00689131 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = A9FC6DB628CC528B00689131 /* Project object */; -} diff --git a/C/test/test.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/C/test/test.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/C/test/test.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/C/test/test.xcodeproj/xcuserdata/marco.xcuserdatad/xcschemes/xcschememanagement.plist b/C/test/test.xcodeproj/xcuserdata/marco.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index ad7f2a4e..00000000 --- a/C/test/test.xcodeproj/xcuserdata/marco.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - SchemeUserState - - test.xcscheme_^#shared#^_ - - orderHint - 0 - - - SuppressBuildableAutocreation - - A9FC6DBD28CC528B00689131 - - primary - - - - - diff --git a/C/test/test/main.c b/C/test/test/main.c deleted file mode 100644 index 53821311..00000000 --- a/C/test/test/main.c +++ /dev/null @@ -1,1110 +0,0 @@ -// -// main.c -// test -// -// Created by Marco Bambini on 10/09/22. -// - -#include -#include -#include -#include -#include "sqcloud.h" - -#define CONNECTION_STRING "sqlitecloud://admin:admin@localhost/compression=1&root_certificate=%2FUsers%2Fmarco%2FDesktop%2FSQLiteCloud%2FPHP%2Fca.pem" -#define CONNECTION_STRING_INSECURE "sqlitecloud://admin:admin@localhost/compression=1&insecure=1" -#define BLOB_FILENAME "/Users/marco/Desktop/test.jpg" -#define BLOB_LEN 445922 -#define BACKUP_FILENAME "/Users/marco/Desktop/test.sqlite" -#define VERBOSE_OUTPUT 0 - -// MARK: - - -static bool do_print (SQCloudConnection *conn, SQCloudResult *res) { - // res NULL means to read error message and error code from conn - SQCLOUD_RESULT_TYPE type = SQCloudResultType(res); - bool result = true; - - switch (type) { - case RESULT_OK: - printf("OK"); - break; - - case RESULT_ERROR: - printf("ERROR: %s (%d)", SQCloudErrorMsg(conn), SQCloudErrorCode(conn)); - result = false; - break; - - case RESULT_NULL: - printf("NULL"); - break; - - case RESULT_STRING: - (SQCloudResultLen(res)) ? printf("%.*s", SQCloudResultLen(res), SQCloudResultBuffer(res)) : printf(""); - break; - - case RESULT_JSON: - case RESULT_INTEGER: - case RESULT_FLOAT: - printf("%.*s", SQCloudResultLen(res), SQCloudResultBuffer(res)); - break; - - case RESULT_ARRAY: - SQCloudArrayDump(res); - break; - - case RESULT_ROWSET: - SQCloudRowsetDump(res, 0, false); - break; - - case RESULT_BLOB: - printf("BLOB data with len: %d", SQCloudResultLen(res)); - break; - } - - printf("\n\n"); - return result; -} - -static bool do_command (SQCloudConnection *conn, char *command, int32_t *int_value, char **string_value, void **blob_value, uint32_t *len, SQCloudResult **result_value) { - printf("%s\n", command); - - SQCloudResult *res = SQCloudExec(conn, command); - if ((int_value) && (SQCloudResultType(res) == RESULT_INTEGER)) *int_value = SQCloudResultInt32(res); - else if ((string_value) && SQCloudResultType(res) == RESULT_STRING) *string_value = SQCloudResultBuffer(res); - else if ((blob_value) && SQCloudResultType(res) == RESULT_BLOB) *blob_value = SQCloudResultBuffer(res); - else if (result_value) *result_value = res; - - if (len) *len = SQCloudResultLen(res); - - bool result = do_print(conn, res); - if (!result_value) SQCloudResultFree(res); - return result; -} - -// MARK: - - -static int test_multiple_commands(SQCloudConnection *conn) { - char *current_sql = NULL; - - if (!do_command(conn, "USE DATABASE mediastore.sqlite;", NULL, NULL, NULL, NULL, NULL)) goto abort_test; - - // Tables - /* - --------|---------------|-------|------|----|--------| - schema | name | type | ncol | wr | strict | - --------|---------------|-------|------|----|--------| - main | Track | table | 9 | 0 | 0 | - main | PlaylistTrack | table | 2 | 0 | 0 | - main | Playlist | table | 2 | 0 | 0 | - main | Artist | table | 2 | 0 | 0 | - main | Customer | table | 13 | 0 | 0 | - main | Employee | table | 15 | 0 | 0 | - main | Genre | table | 2 | 0 | 0 | - main | Invoice | table | 9 | 0 | 0 | - main | Album | table | 3 | 0 | 0 | - main | InvoiceLine | table | 5 | 0 | 0 | - main | MediaType | table | 2 | 0 | 0 | - --------|---------------|-------|------|----|--------| - */ - - // ==================================== - // TEST MULTIPLE READ SQLITE STATEMENTS ==> OK - // ==================================== - SQCloudResult *res1 = NULL; - char *sql = "SELECT * FROM Artist"; - if (!do_command(conn, sql, NULL, NULL, NULL, NULL, &res1)) goto abort_test; - - SQCloudResult *res2 = NULL; - current_sql = "SELECT * FROM Track; SELECT 1; SELECT * FROM Album; SELECT * FROM Artist"; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, &res2)) goto abort_test; - - if (SQCloudRowsetCompare(res1, res2) == false) goto abort_test; - - // ====================================== - // TEST MULTIPLE READ BUILT-IN STATEMENTS ==> OK - // ====================================== - res1 = NULL; - sql = "LIST TABLES"; - if (!do_command(conn, sql, NULL, NULL, NULL, NULL, &res1)) goto abort_test; - - res2 = NULL; - current_sql = "LIST INFO; LIST DATABASES; LIST TABLES;"; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, &res2)) goto abort_test; - - if (SQCloudRowsetCompare(res1, res2) == false) goto abort_test; - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - - // ============================================= - // TEST MULTIPLE READ SQLITE/BUILT-IN STATEMENTS ==> OK - // ============================================= - res1 = NULL; - sql = "SELECT * FROM Artist"; - if (!do_command(conn, sql, NULL, NULL, NULL, NULL, &res1)) goto abort_test; - - res2 = NULL; - current_sql = "SELECT * FROM Track; SELECT 1; LIST TABLES; SELECT * FROM Artist"; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, &res2)) goto abort_test; - - if (SQCloudRowsetCompare(res1, res2) == false) goto abort_test; - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - - // ============================================= - // TEST MULTIPLE READ SQLITE/BUILT-IN STATEMENTS ==> OK - // ============================================= - res1 = NULL; - sql = "SELECT * FROM Artist"; - if (!do_command(conn, sql, NULL, NULL, NULL, NULL, &res1)) goto abort_test; - - res2 = NULL; - current_sql = "LIST DATABASES; SELECT 1; LIST TABLES; SELECT * FROM Artist"; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, &res2)) goto abort_test; - - if (SQCloudRowsetCompare(res1, res2) == false) goto abort_test; - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - - // ============================================= - // TEST MULTIPLE READ SQLITE/BUILT-IN STATEMENTS - // ============================================= - res1 = NULL; - sql = "SELECT * FROM Artist"; - if (!do_command(conn, sql, NULL, NULL, NULL, NULL, &res1)) goto abort_test; - - res2 = NULL; - current_sql = "VM COMPILE 'SELECT 1';SELECT * FROM Artist;"; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, &res2)) goto abort_test; - - if (SQCloudRowsetCompare(res1, res2) == false) goto abort_test; - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - - // ============================================= - // TEST MULTIPLE READ SQLITE/BUILT-IN STATEMENTS (VM COMPILE) - // ============================================= - res1 = NULL; - sql = "SELECT * FROM Artist"; - if (!do_command(conn, sql, NULL, NULL, NULL, NULL, &res1)) goto abort_test; - - res2 = NULL; - current_sql = "VM COMPILE 'SELECT 1';"; - do_command(conn, current_sql, NULL, NULL, NULL, NULL, &res2); - int vmid = -1; - if ((SQCloudResultType(res2) == RESULT_ARRAY) && SQCloudArrayValueType(res2, 1) == VALUE_INTEGER) vmid = SQCloudArrayInt32Value(res2, 1); - else goto abort_test; - SQCloudResultFree(res2); - - char command[256]; - snprintf(command, sizeof(command), "VM STEP %d;SELECT * FROM Artist;", vmid); - current_sql = command; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, &res2)) goto abort_test; - - if (SQCloudRowsetCompare(res1, res2) == false) goto abort_test; - - snprintf(command, sizeof(command), "VM FINALIZE %d;", vmid); - current_sql = command; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, NULL)) goto abort_test; - - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - - // ============================================= - // TEST MULTIPLE READ SQLITE/BUILT-IN STATEMENTS (VM EXECUTE) - // ============================================= - res1 = NULL; - sql = "VM EXECUTE 'SELECT * FROM Artist'"; - if (!do_command(conn, sql, NULL, NULL, NULL, NULL, &res1)) goto abort_test; - - res2 = NULL; - current_sql = "VM EXECUTE 'SELECT 1;SELECT 2';SELECT * FROM Artist;"; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, &res2)) goto abort_test; - - if (SQCloudRowsetCompare(res1, res2) == false) goto abort_test; - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - - // ============================================= - // TEST MULTIPLE SQLITE INSIDE VM EXECUTE - // ============================================= - res1 = NULL; - sql = "SELECT * FROM Artist"; - if (!do_command(conn, sql, NULL, NULL, NULL, NULL, &res1)) goto abort_test; - - res2 = NULL; - current_sql = "VM EXECUTE 'SELECT 1;SELECT * FROM Artist';"; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, &res2)) goto abort_test; - - if (SQCloudRowsetCompare(res1, res2) == false) goto abort_test; - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - - // ============================================= - // TEST MULTIPLE READ SQLITE/BUILT-IN STATEMENTS (BLOB OPEN) - // ============================================= - res1 = NULL; - sql = "SELECT * FROM Artist"; - if (!do_command(conn, sql, NULL, NULL, NULL, NULL, &res1)) goto abort_test; - - res2 = NULL; - current_sql = "BLOB OPEN main TABLE Artist COLUMN Name ROWID 1 RWFLAG 0;SELECT * FROM Artist;"; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, &res2)) goto abort_test; - - if (SQCloudRowsetCompare(res1, res2) == false) goto abort_test; - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - - // ============================================= - // TEST MULTIPLE READ SQLITE/BUILT-IN STATEMENTS (BLOB BYTES RWFLAg 0) - // ============================================= - res1 = NULL; - sql = "SELECT * FROM Artist"; - if (!do_command(conn, sql, NULL, NULL, NULL, NULL, &res1)) goto abort_test; - - res2 = NULL; - current_sql = "BLOB OPEN main TABLE Artist COLUMN Name ROWID 1 RWFLAG 0;"; - int blobid = -1; - do_command(conn, current_sql, &blobid, NULL, NULL, NULL, NULL); - snprintf(command, sizeof(command), "BLOB BYTES %d; SELECT * FROM Artist;", blobid); - current_sql = command; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, &res2)) goto abort_test; - - if (SQCloudRowsetCompare(res1, res2) == false) goto abort_test; - - snprintf(command, sizeof(command), "BLOB CLOSE %d;", blobid); - current_sql = command; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, NULL)) goto abort_test; - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - - // ============================================= - // TEST MULTIPLE READ SQLITE/BUILT-IN STATEMENTS (BLOB BYTES RWFLAG 1) - // ============================================= - res1 = NULL; - sql = "SELECT * FROM Artist"; - if (!do_command(conn, sql, NULL, NULL, NULL, NULL, &res1)) goto abort_test; - - res2 = NULL; - current_sql = "BLOB OPEN main TABLE Artist COLUMN Name ROWID 1 RWFLAG 1;"; - blobid = -1; - do_command(conn, current_sql, &blobid, NULL, NULL, NULL, NULL); - snprintf(command, sizeof(command), "BLOB BYTES %d; SELECT * FROM Artist;", blobid); - current_sql = command; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, &res2)) goto abort_test; - - if (SQCloudRowsetCompare(res1, res2) == false) goto abort_test; - - snprintf(command, sizeof(command), "BLOB CLOSE %d;", blobid); - current_sql = command; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, NULL)) goto abort_test; - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - - - // ============================================= - // TEST MULTIPLE READ SQLITE/BUILT-IN STATEMENTS (BLOB READ RWFLAG 0) - // ============================================= - res1 = NULL; - sql = "SELECT * FROM Artist"; - if (!do_command(conn, sql, NULL, NULL, NULL, NULL, &res1)) goto abort_test; - - res2 = NULL; - current_sql = "BLOB OPEN main TABLE Artist COLUMN Name ROWID 1 RWFLAG 0;"; - blobid = -1; - do_command(conn, current_sql, &blobid, NULL, NULL, NULL, NULL); - snprintf(command, sizeof(command), "BLOB BYTES %d; SELECT * FROM Artist;", blobid); - current_sql = command; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, &res2)) goto abort_test; - - if (SQCloudRowsetCompare(res1, res2) == false) goto abort_test; - - snprintf(command, sizeof(command), "BLOB CLOSE %d;", blobid); - current_sql = command; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, NULL)) goto abort_test; - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - - // ============================================= - // TEST MULTIPLE READ SQLITE/BUILT-IN STATEMENTS (BLOB READ RWFLAG 1) - // ============================================= - res1 = NULL; - sql = "SELECT * FROM Artist"; - if (!do_command(conn, sql, NULL, NULL, NULL, NULL, &res1)) goto abort_test; - - res2 = NULL; - current_sql = "BLOB OPEN main TABLE Artist COLUMN Name ROWID 1 RWFLAG 1;"; - blobid = -1; - do_command(conn, current_sql, &blobid, NULL, NULL, NULL, NULL); - snprintf(command, sizeof(command), "BLOB BYTES %d; SELECT * FROM Artist;", blobid); - current_sql = command; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, &res2)) goto abort_test; - - if (SQCloudRowsetCompare(res1, res2) == false) goto abort_test; - - snprintf(command, sizeof(command), "BLOB CLOSE %d;", blobid); - current_sql = command; - if (!do_command(conn, current_sql, NULL, NULL, NULL, NULL, NULL)) goto abort_test; - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - - return 0; - -abort_test: - printf("%s FAILED!\n\n", current_sql); - exit(-1); - return -1; -} - -// MARK: - - -static bool test_read_blob (SQCloudConnection *conn) { - const char *filename = BLOB_FILENAME; - unlink(filename); - FILE *f = fopen(filename, "w"); - if (!f) {perror("Error creating file in test_read_blob"); return false;} - - int32_t blob_index = -1; - - int32_t rowid = 0; - SQCloudResult *res; - if (!do_command(conn, "SELECT MAX(rowid) FROM images;", &rowid, NULL, NULL, NULL, &res)) return false; - if ((SQCloudResultType(res) == RESULT_ROWSET) && SQCloudRowsetValueType(res, 0, 0) == VALUE_INTEGER) rowid = SQCloudRowsetInt32Value(res, 0, 0); - - // BLOB OPEN TABLE COLUMN ROWID RWFLAG - char command[256]; - snprintf(command, sizeof(command), "BLOB OPEN main TABLE images COLUMN picture ROWID %d RWFLAG 0;", rowid); - if (!do_command(conn, command, &blob_index, NULL, NULL, NULL, NULL)) return false; - - // SELECT length(picture) FROM images WHERE rowid=2; => 445922 - int32_t len = 0, lblob = BLOB_LEN; - - // BLOB BYTES - snprintf(command, sizeof(command), "BLOB BYTES %d", blob_index); - if (!do_command(conn, command, &len, NULL, NULL, NULL, NULL)) return false; - if (len != lblob) {printf("BLOB size is wrong %d %d (test_read_blob)\n", len, lblob); return false;} - - // PERFORM 3 reads - char buffer[1024*100]; - int offset = 0; - int blen = sizeof(buffer); - - while (1) { - void *data = NULL; - uint32_t len = 0; - - // BLOB READ SIZE OFFSET - snprintf(command, sizeof(command), "BLOB READ %d SIZE %d OFFSET %d", blob_index, blen, offset); - if (!do_command(conn, command, NULL, NULL, &data, &len, NULL)) return false; - - if (blen != len) {printf("BLOB read returned a wrong len %d != %d\n", blen, len); return false;} - - size_t fwrote = fwrite(data, blen, 1, f); - if (fwrote != 1) {perror("Error writing BLOB data"); return false;} - - offset += blen; - if (offset == lblob) break; - if (lblob - offset < blen) blen = lblob - offset; - } - - // close test file - fclose(f); - - // finalize BLOB vm - snprintf(command, sizeof(command), "BLOB CLOSE %d", blob_index); - if (!do_command(conn, command, NULL, NULL, NULL, NULL, NULL)) return false; - - return true; -} - -static bool test_write_blob (SQCloudConnection *conn) { - const char *filename = BLOB_FILENAME; - FILE *f = fopen(filename, "rb"); - if (!f) {perror("Error reading file in test_write_blob"); return false;} - - char command[256]; - snprintf(command, sizeof(command), "INSERT INTO images (picture) VALUES (zeroblob(%d));", BLOB_LEN); - if (!do_command(conn, command, NULL, NULL, NULL, NULL, NULL)) return false; - - int32_t rowid = 0; - if (!do_command(conn, "DATABASE GET ROWID;", &rowid, NULL, NULL, NULL, NULL)) return false; - - int32_t blob_index = 0; - - // BLOB OPEN TABLE COLUMN ROWID RWFLAG - snprintf(command, sizeof(command), "BLOB OPEN main TABLE images COLUMN picture ROWID %d RWFLAG 1;", rowid); - if (!do_command(conn, command, &blob_index, NULL, NULL, NULL, NULL)) return false; - - // prepare buffer - char buffer[1024*100]; - int blen = sizeof(buffer); - int offset = 0; - - // loop to read input file and write BLOB - while (!feof(f)) { - size_t nbytes = fread(buffer, 1, blen, f); - - const char *values[] = {buffer}; - uint32_t len[] = {(uint32_t)nbytes}; - SQCLOUD_VALUE_TYPE types[] = {VALUE_BLOB}; - - // BLOB WRITE OFFSET DATA - snprintf(command, sizeof(command), "BLOB WRITE %d OFFSET %d DATA ?;", blob_index, offset); - - printf("%s\n", command); - SQCloudResult *result = SQCloudExecArray(conn, command, values, len, types, 1); - SQCloudResultDump(conn, result); - - offset += nbytes; - } - - if (offset != BLOB_LEN) {printf("BLOB size is wrong %d %d (test_write_blob)\n", offset, BLOB_LEN); return false;} - - // close test file - fclose(f); - - // BLOB BYTES - int32_t len = 0; - snprintf(command, sizeof(command), "BLOB BYTES %d", blob_index); - if (!do_command(conn, command, &len, NULL, NULL, NULL, NULL)) return false; - if (len != BLOB_LEN) {printf("BLOB size is wrong %d %d (test_write_blob 2)\n", len, BLOB_LEN); return false;} - - // finalize BLOB vm - snprintf(command, sizeof(command), "BLOB CLOSE %d", blob_index); - if (!do_command(conn, command, NULL, NULL, NULL, NULL, NULL)) return false; - - return true; -} - -static int test_blob (SQCloudConnection *conn) { - if (!do_command(conn, "USE DATABASE images.sqlite;", NULL, NULL, NULL, NULL, NULL)) goto abort_test; - if (!test_read_blob(conn)) goto abort_test; - if (!test_write_blob(conn)) goto abort_test; - return 0; - -abort_test: - exit(-1); - return -1; -} - -// MARK: - - -static int test_array_create_apikey (SQCloudConnection *conn) { - char *current_sql = NULL; - const char *val1 = "apiusertest123"; - const char *val2 = "newkey1"; - - SQCloudResult *res1 = SQCloudExec(conn, "LIST APIKEYS"); - SQCloudResultDump(conn, res1); - - const char *values[] = {val1, val1, val2}; - uint32_t len[] = {(uint32_t)strlen(val1), (uint32_t)strlen(val1), (uint32_t)strlen(val2)}; - SQCLOUD_VALUE_TYPE types[] = {VALUE_TEXT, VALUE_TEXT, VALUE_TEXT}; - current_sql = "CREATE USER ? PASSWORD 123456; CREATE APIKEY USER ? NAME ?"; - SQCloudResult *res = SQCloudExecArray(conn, current_sql, values, len, types, 3); - SQCloudResultDump(conn, res); - if (SQCloudResultType(res) != RESULT_STRING) goto abort_test; - SQCloudResultFree(res); - - SQCloudResult *res2 = SQCloudExec(conn, "LIST APIKEYS"); - SQCloudResultDump(conn, res2); - if (SQCloudRowsetCompare(res1, res2) == true) goto abort_test; - - const char *val3 = "newkey2"; - const char *val4 = "0"; - const char *val5 = "2022-12-31 23:59:59"; - const char *values1[] = {val1, val3, val4, val5}; - uint32_t len1[] = {(uint32_t)strlen(val1), (uint32_t)strlen(val3), (uint32_t)strlen(val4), (uint32_t)strlen(val5)}; - SQCLOUD_VALUE_TYPE types1[] = {VALUE_TEXT, VALUE_TEXT, VALUE_INTEGER, VALUE_TEXT}; - current_sql = "CREATE APIKEY USER ? NAME ? RESTRICTION ? EXPIRATION ?"; - res = SQCloudExecArray(conn, current_sql, values1, len1, types1, 4); - SQCloudResultDump(conn, res); - if (SQCloudResultType(res) != RESULT_STRING) goto abort_test; - SQCloudResultFree(res); - - SQCloudResult *res3 = SQCloudExec(conn, "LIST APIKEYS"); - SQCloudResultDump(conn, res3); - if (SQCloudRowsetCompare(res2, res3) == true) goto abort_test; - - current_sql = "DROP USER ?;"; - res = SQCloudExecArray(conn, current_sql, values, len, types, 1); - if (SQCloudResultType(res) != RESULT_OK) goto abort_test; - SQCloudResultFree(res); - - SQCloudResult *res4 = SQCloudExec(conn, "LIST APIKEYS"); - SQCloudResultDump(conn, res3); - if (SQCloudRowsetCompare(res1, res4) == false) goto abort_test; - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - SQCloudResultFree(res3); - SQCloudResultFree(res4); - - return 0; - -abort_test: - printf("%s FAILED! (test_array_create_apikey)\n\n", current_sql); - exit(-1); - return -1; -} - -static int test_array_create_db (SQCloudConnection *conn) { - char *current_sql = NULL; - const char *val1 = "newdbtest1.sqlite"; - - SQCloudResult *res1 = SQCloudExec(conn, "LIST DATABASES"); - SQCloudResultDump(conn, res1); - - const char *values[] = {val1}; - uint32_t len[] = {(uint32_t)strlen(val1)}; - SQCLOUD_VALUE_TYPE types[] = {VALUE_TEXT}; - current_sql = "CREATE DATABASE ? IF NOT EXISTS;"; - SQCloudResult *res = SQCloudExecArray(conn, current_sql, values, len, types, 1); - if (SQCloudResultType(res) != RESULT_OK) goto abort_test; - SQCloudResultFree(res); - - SQCloudResult *res2 = SQCloudExec(conn, "LIST DATABASES"); - SQCloudResultDump(conn, res2); - if (SQCloudRowsetCompare(res1, res2) == true) goto abort_test; - - current_sql = "DROP DATABASE ?;"; - res = SQCloudExecArray(conn, current_sql, values, len, types, 1); - if (SQCloudResultType(res) != RESULT_OK) goto abort_test; - SQCloudResultFree(res); - - SQCloudResult *res3 = SQCloudExec(conn, "LIST DATABASES"); - SQCloudResultDump(conn, res3); - if (SQCloudRowsetCompare(res1, res3) == false) goto abort_test; - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - SQCloudResultFree(res3); - - return 0; - -abort_test: - printf("%s FAILED! (test_array_create_db)\n\n", current_sql); - exit(-1); - return -1; -} - -static int test_array_multicommands_db (SQCloudConnection *conn) { - char *current_sql = NULL; - - { - const char *val1 = "newdbtest1.sqlite"; - const char *val2 = "newdbtest2.sqlite"; - const char *val3 = "newdbtest2.sqlite"; - const char *val4 = "abc"; - const char *val5 = "def"; - - SQCloudResult *res1 = SQCloudExec(conn, "LIST DATABASES"); - SQCloudResultDump(conn, res1); - - const char *values[] = {val1, val2, val3, val4, val5}; - uint32_t len[] = {(uint32_t)strlen(val1), (uint32_t)strlen(val2), (uint32_t)strlen(val3), (uint32_t)strlen(val4), (uint32_t)strlen(val5)}; - SQCLOUD_VALUE_TYPE types[] = {VALUE_TEXT, VALUE_TEXT, VALUE_TEXT, VALUE_TEXT, VALUE_TEXT}; - current_sql = "CREATE DATABASE ? IF NOT EXISTS; CREATE DATABASE ? IF NOT EXISTS; USE DATABASE ?; CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY, b); INSERT INTO t1 (b) VALUES (?); INSERT INTO t1 (b) VALUES (?)"; - SQCloudResult *res = SQCloudExecArray(conn, current_sql, values, len, types, 5); - SQCloudResultDump(conn, res); - if (SQCloudResultType(res) != RESULT_OK) goto abort_test; - SQCloudResultFree(res); - - SQCloudResult *res2 = SQCloudExec(conn, "LIST DATABASES"); - SQCloudResultDump(conn, res2); - if (SQCloudRowsetCompare(res1, res2) == true) goto abort_test; - - current_sql = "UNUSE DATABASE; DROP DATABASE ?; DROP DATABASE ?"; - res = SQCloudExecArray(conn, current_sql, values, len, types, 2); - if (SQCloudResultType(res) != RESULT_OK) goto abort_test; - SQCloudResultFree(res); - - SQCloudResult *res3 = SQCloudExec(conn, "LIST DATABASES"); - SQCloudResultDump(conn, res3); - if (SQCloudRowsetCompare(res1, res3) == false) goto abort_test; - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - SQCloudResultFree(res3); - } - - { - const char *val1 = "newdbtest3.sqlite"; - - SQCloudResult *res1 = SQCloudExec(conn, "LIST DATABASES"); - SQCloudResultDump(conn, res1); - - const char *values[] = {val1}; - uint32_t len[] = {(uint32_t)strlen(val1)}; - SQCLOUD_VALUE_TYPE types[] = {VALUE_TEXT}; - current_sql = "CREATE DATABASE ? IF NOT EXISTS"; - SQCloudResult *res = SQCloudExecArray(conn, current_sql, values, len, types, 1); - SQCloudResultDump(conn, res); - if (SQCloudResultType(res) != RESULT_OK) goto abort_test; - SQCloudResultFree(res); - - SQCloudResult *res2 = SQCloudExec(conn, "LIST DATABASES"); - SQCloudResultDump(conn, res2); - if (SQCloudRowsetCompare(res1, res2) == true) goto abort_test; - - current_sql = "USE DATABASE ?;"; - res = SQCloudExecArray(conn, current_sql, values, len, types, 1); - if (SQCloudResultType(res) != RESULT_OK) goto abort_test; - SQCloudResultFree(res); - - current_sql = "CREATE TABLE t1 (a INTEGER PRIMARY KEY, b)"; - res = SQCloudExec(conn, current_sql); - if (SQCloudResultType(res) != RESULT_OK) goto abort_test; - SQCloudResultFree(res); - - current_sql = "LIST TABLES"; - res = SQCloudExec(conn, current_sql); - SQCloudResultDump(conn, res); - if (SQCloudResultType(res) != RESULT_ROWSET || SQCloudRowsetRows(res) != 1) goto abort_test; - SQCloudResultFree(res); - - current_sql = "UNUSE DATABASE; DROP DATABASE ?"; - res = SQCloudExecArray(conn, current_sql, values, len, types, 1); - if (SQCloudResultType(res) != RESULT_OK) goto abort_test; - SQCloudResultFree(res); - - SQCloudResult *res3 = SQCloudExec(conn, "LIST DATABASES"); - SQCloudResultDump(conn, res3); - if (SQCloudRowsetCompare(res1, res3) == false) goto abort_test; - - SQCloudResultFree(res1); - SQCloudResultFree(res2); - SQCloudResultFree(res3); - } - - return 0; - -abort_test: - printf("%s FAILED! (test_array_multicommands_db)\n\n", current_sql); - exit(-1); - return -1; -} - -static int test_array_multicommand (SQCloudConnection *conn) { - char *current_sql = NULL; - - const char *val1 = "testdb1.sqlite"; - const char *values1[] = {val1, val1}; - uint32_t len1[] = {(uint32_t)strlen(val1), (uint32_t)strlen(val1)}; - SQCLOUD_VALUE_TYPE types1[] = {VALUE_TEXT, VALUE_TEXT}; - - current_sql = "CREATE DATABASE ?; USE DATABASE ?; CREATE TABLE t1 (a INTEGER PRIMARY KEY, b);"; - SQCloudResult *result = SQCloudExecArray(conn, current_sql, values1, len1, types1, 2); - if (SQCloudResultType(result) != RESULT_OK) { - SQCloudResultDump(conn, result); - goto abort_test; - } - SQCloudResultFree(result); - - const char *val2 = "aaa"; - const char *values2[] = {val2}; - uint32_t len2[] = {(uint32_t)strlen(val2)}; - SQCLOUD_VALUE_TYPE types2[] = {VALUE_TEXT}; - - current_sql = "SELECT * FROM t1; INSERT INTO t1 (b) VALUES (?)"; - result = SQCloudExecArray(conn, current_sql, values2, len2, types2, 1); - if (SQCloudResultType(result) != RESULT_OK) { - SQCloudResultDump(conn, result); - goto abort_test; - } - SQCloudResultFree(result); - - result = SQCloudExec(conn, "UNUSE DATABASE"); - if (SQCloudResultType(result) != RESULT_OK) { - SQCloudResultDump(conn, result); - goto abort_test; - } - SQCloudResultFree(result); - - const char *val3 = "bbb"; - const char *values3[] = {val1, val3}; - uint32_t len3[] = {(uint32_t)strlen(val1), (uint32_t)strlen(val3)}; - SQCLOUD_VALUE_TYPE types3[] = {VALUE_TEXT, VALUE_TEXT}; - - current_sql = "SWITCH DATABASE ?; INSERT INTO t1 (b) VALUES (?)"; - result = SQCloudExecArray(conn, current_sql, values3, len3, types3, 2); - if (SQCloudResultType(result) != RESULT_OK) { - SQCloudResultDump(conn, result); - goto abort_test; - } - SQCloudResultFree(result); - - current_sql = "SELECT * FROM t1;"; - result = SQCloudExec(conn, current_sql); - if (SQCloudResultType(result) == RESULT_OK) { - SQCloudResultDump(conn, result); - goto abort_test; - } - SQCloudResultFree(result); - - const char *val4 = "ccc"; - const char *values4[] = {val1, val4}; - uint32_t len4[] = {(uint32_t)strlen(val1), (uint32_t)strlen(val4)}; - SQCLOUD_VALUE_TYPE types4[] = {VALUE_TEXT, VALUE_TEXT}; - current_sql = "SWITCH DATABASE ?; INSERT INTO t1 (b) VALUES (?)"; - result = SQCloudExecArray(conn, current_sql, values4, len4, types4, 2); - if (SQCloudResultType(result) != RESULT_OK) { - SQCloudResultDump(conn, result); - goto abort_test; - } - SQCloudResultFree(result); - - current_sql = "DROP DATABASE ?;"; - result = SQCloudExecArray(conn, current_sql, values3, len3, types3, 1); - if (SQCloudResultType(result) != RESULT_OK) { - SQCloudResultDump(conn, result); - goto abort_test; - } - SQCloudResultFree(result); - - return 0; - -abort_test: - printf("%s FAILED! (test_array_multicommand)\n\n", current_sql); - exit(-1); - return -1; -} - -static int test_array (SQCloudConnection *conn) { - // built-in with 1 binding - { - // USE DATABASE - const char *dbname = "mediastore.sqlite"; - const char *values[] = {dbname}; - uint32_t len[] = {(uint32_t)strlen(dbname)}; - SQCLOUD_VALUE_TYPE types[] = {VALUE_TEXT}; - - const char *command = "USE DATABASE ?"; - printf("%s\n", command); - SQCloudResult *result = SQCloudExecArray(conn, command, values, len, types, 1); - SQCloudResultDump(conn, result); - } - - // SQLite with 1 binding - { - // SELECT * FROM Artist - const char *value = "100"; - const char *values[] = {value}; - uint32_t len[] = {(uint32_t)strlen(value)}; - SQCLOUD_VALUE_TYPE types[] = {VALUE_INTEGER}; - - const char *command = "SELECT * FROM Artist WHERE ArtistId >= ?"; - printf("%s\n", command); - SQCloudResult *result = SQCloudExecArray(conn, command, values, len, types, 1); - SQCloudResultDump(conn, result); - - int rc = 0; - if (SQCloudResultType(result) != RESULT_ROWSET || SQCloudRowsetRows(result) != 176) rc = -1; - - SQCloudResultFree(result); - - if (rc != 0) return rc; - } - - // SQLite with 2 bindings - { - // SELECT * FROM Artist - const char *value1 = "100"; - const char *value2 = "200"; - const char *values[] = {value1, value2}; - uint32_t len[] = {(uint32_t)strlen(value1), (uint32_t)strlen(value2)}; - SQCLOUD_VALUE_TYPE types[] = {VALUE_INTEGER, VALUE_INTEGER}; - - const char *command = "SELECT * FROM Artist WHERE ArtistId >= ? AND ArtistId <= ?"; - printf("%s\n", command); - SQCloudResult *result = SQCloudExecArray(conn, command, values, len, types, 2); - SQCloudResultDump(conn, result); - - int rc = 0; - if (SQCloudResultType(result) != RESULT_ROWSET || SQCloudRowsetRows(result) != 101) rc = -1; - - SQCloudResultFree(result); - - if (rc != 0) return rc; - } - - // built-in with 4 bindings - { - // SET APIKEY [NAME ] [RESTRICTION ] [EXPIRATION ] - const char *key = "aJcdAL6P1JwJHquTP5iK1ahk7b3tAicBBufPSmnkIb4"; - const char *name = "Bind Test"; - const char *restriction = "1"; - const char *expiration = "2022-09-21 18:27:29"; - const char *values[] = {key, name, restriction, expiration}; - uint32_t len[] = {(uint32_t)strlen(key), (uint32_t)strlen(name), (uint32_t)strlen(restriction), (uint32_t)strlen(expiration)}; - SQCLOUD_VALUE_TYPE types[] = {VALUE_TEXT, VALUE_TEXT, VALUE_TEXT, VALUE_TEXT}; - - const char *command = "SET APIKEY ? NAME ? RESTRICTION ? EXPIRATION ?"; - printf("%s\n", command); - SQCloudResult *result = SQCloudExecArray(conn, command, values, len, types, 4); - SQCloudResultDump(conn, result); - SQCloudResultFree(result); - } - - // VM - { - const char *command = "SELECT * FROM Artist WHERE ArtistId >= ? AND ArtistId <= ?"; - printf("%s\n", command); - SQCloudVM *vm = SQCloudVMCompile (conn, command, -1, NULL); - - bool result = SQCloudVMBindInt (vm, 1, 100); - result = SQCloudVMBindInt (vm, 2, 105); - - /*SQCloudResType type = */SQCloudVMStep(vm); - SQCloudResult *r = SQCloudVMResult(vm); - SQCloudResultDump(conn, r); - - int rc = 0; - if (SQCloudResultType(r) != RESULT_ROWSET || SQCloudRowsetRows(r) != 6) rc = -1; - - SQCloudVMClose(vm); - - if (rc != 0) return rc; - } - - return 0; -} - -// MARK: - - -static int test_backup (SQCloudConnection *conn) { - if (!do_command(conn, "USE DATABASE mediastore.sqlite;", NULL, NULL, NULL, NULL, NULL)) return -1; - - const char *filename = BACKUP_FILENAME; - unlink(filename); - FILE *f = fopen(filename, "w"); - if (!f) {perror("Error creating file in test_backup"); return -1;} - - // BACKUP INIT [] [SOURCE ] - SQCloudResult *result = NULL; - if (!do_command(conn, "BACKUP INIT", NULL, NULL, NULL, NULL, &result)) return -1; - - // sanity check - if (SQCloudResultType(result) != RESULT_ARRAY) { - printf("Wrong result type\n"); - return -1; - } - - // extract information - int32_t index = SQCloudArrayInt32Value(result, 1); - int32_t page_size = SQCloudArrayInt32Value(result, 2); - - #if VERBOSE_OUTPUT - int32_t page_count = SQCloudArrayInt32Value(result, 3); - int counter = 0; - printf("Page Size: %d\n", page_size); - printf("Page Count: %d\n", page_count); - #endif - - SQCloudResultFree(result); - - char command[512]; - int64_t current_offset = 0; - int npages = 40; - int32_t total = 0; - - while (1) { - // BACKUP STEP PAGES - snprintf(command, sizeof(command), "BACKUP STEP %d PAGES %d;", index, npages); - if (!do_command(conn, command, NULL, NULL, NULL, NULL, &result)) goto abort_backup; - - SQCLOUD_RESULT_TYPE rtype = SQCloudResultType(result); - if (rtype != RESULT_ARRAY) goto abort_backup; - - /* - [0] -> TYPE - [1] -> INDEX - [2] -> PAGE_TOTALE - [3] -> PAGE_REMAINING - [4] -> PAGE_COUNTER - [5] -> BLOB - */ - - #if VERBOSE_OUTPUT - int32_t page_totals = SQCloudArrayInt32Value(result, 2); - #endif - int32_t page_remaining = SQCloudArrayInt32Value(result, 3); - int32_t page_counter = SQCloudArrayInt32Value(result, 4); - - #if VERBOSE_OUTPUT - printf("Counter: %d\n", ++counter); - printf("Page Total: %d\n", page_totals); - printf("Page Remaining: %d\n", page_remaining); - printf("Page Counter: %d\n", page_counter); - #endif - - uint32_t blen = 0; - char *data = SQCloudArrayValue (result, 5, &blen); - #if VERBOSE_OUTPUT - printf("Len: %d\n", blen); - #endif - - // sanity check - if (blen != ((page_size * page_counter) + (page_counter * sizeof(int64_t)))) { - printf("Block size error\n"); - goto abort_backup; - } - - char *p = data; - for (int i=0; i -Password: - -``` - -### Setting up the development environment -```console -go env -w GO111MODULE=on -export GOPATH= -echo $GOPATH -``` - -### Run the test for the SDK -If you want to run the Test programs: `make test` - -### Building the CLI App -To build the CLI App (Warning: not fully functional, this is officially Step 1), you have to enter: `make cli` - -### Build all at the same time: -If you want to do all at the same time: `make all` - -## Documentation -If you want to see the Documentation: `make doc` - Warning: A browser window will open and display the documentation to you. The Documentation is updated live while coding. To stop the live mode, press CRTL-C on the command line. - -## Development helpers -- Check files with gosec: `make checksec` -- Open the repo in github: `make github`. -- See changes: `make diff` -- Clean dependencies and precompiled code: `make clean` \ No newline at end of file diff --git a/GO/cli/ComandLineCompatibility.txt b/GO/cli/ComandLineCompatibility.txt deleted file mode 100644 index c207d4b2..00000000 --- a/GO/cli/ComandLineCompatibility.txt +++ /dev/null @@ -1,225 +0,0 @@ -✅ = Implemented, directly or indrectly available -🤔 = Not Implemented / Maybe later? -👎 = Decided agains implementation, not usefull -❌ = Does not apply / will never be implemented - -SQLite3 - -🤔 -A ARGS... run ".archive ARGS" and exit -❌ -append append the database to the end of the file -👎 -ascii set output mode to 'ascii' -✅ -bail stop after hitting an error -🤔 -batch force batch I/O -✅ -box set output mode to 'box' -✅ -column set output mode to 'column' -✅ -cmd COMMAND run "COMMAND" before reading stdin -✅ -csv set output mode to 'csv' -❌ -deserialize open the database using sqlite3_deserialize() -✅ -echo print commands before execution -✅ -init FILENAME read/process named file -✅ -[no]header turn headers on or off -✅ -help show this message -✅ -html set output mode to HTML -👎 -interactive force interactive I/O -✅ -json set output mode to 'json' -✅ -line set output mode to 'line' -✅ -list set output mode to 'list' -❌ -lookaside SIZE N use N entries of SZ bytes for lookaside memory -✅ -markdown set output mode to 'markdown' -❌ -maxsize N maximum size for a --deserialize database -❌ -memtrace trace all memory allocations and deallocations -❌ -mmap N default mmap size set to N -✅ -newline SEP set output row separator. Default: '\n' -❌ -nofollow refuse to open symbolic links to database files -✅ -nullvalue TEXT set text string for NULL values. Default '' -❌ -pagecache SIZE N use N slots of SZ bytes each for page cache memory -✅ -quote set output mode to 'quote' -❌ -readonly open the database read-only -✅ -separator SEP set output column separator. Default: '|' -👎 -stats print memory stats before each finalize -✅ -table set output mode to 'table' -✅ -tabs set output mode to 'tabs' -✅ -version show SQLite version -❌ -vfs NAME use NAME as the default VFS -❌ -zip open the file as a ZIP Archive - - -PostgreSQL - -✅ -c, --command=COMMAND run only single command (SQL or internal) and exit -✅ -d, --dbname=DBNAME database name to connect to (default: "ubuntu") -✅ -f, --file=FILENAME execute commands from file, then exit -✅ -l, --list list available databases, then exit -🤔 -v, --set=, --variable=NAME=VALUE -🤔 set psql variable NAME to VALUE -🤔 (e.g., -v ON_ERROR_STOP=1) -✅ -V, --version output version information, then exit -👎 -X, --no-psqlrc do not read startup file (~/.psqlrc) -👎 -1 ("one"), --single-transaction -👎 execute as a single transaction (if non-interactive) -✅ -?, --help[=options] show this help, then exit -👎 --help=commands list backslash commands, then exit -👎 --help=variables list special variables, then exit - -Input and output options: -👎 -a, --echo-all echo all input from script -👎 -b, --echo-errors echo failed commands -✅ -e, --echo-queries echo commands sent to server -👎 -E, --echo-hidden display queries that internal commands generate -🤔 -L, --log-file=FILENAME send session log to file -🤔 -E, --echo-hidden display queries that internal commands generate -🤔 -n, --no-readline disable enhanced command line editing (readline) -✅ -o, --output=FILENAME send query results to file (or |pipe) -✅ -q, --quiet run quietly (no messages, only query output) -🤔 -s, --single-step single-step mode (confirm each query) -❌ -S, --single-line single-line mode (end of line terminates SQL command) - -Output format options: -🤔 -A, --no-align unaligned table output mode -✅ -F, --field-separator=STRING -✅ field separator for unaligned output (default: "|") -✅ -H, --html HTML table output mode -🤔 -P, --pset=VAR[=ARG] set printing option VAR to ARG (see \pset command) -✅ -R, --record-separator=STRING -✅ record separator for unaligned output (default: newline) -✅ -t, --tuples-only print rows only -🤔 -T, --table-attr=TEXT set HTML table tag attributes (e.g., width, border) -🤔 -x, --expanded turn on expanded table output -👎 -z, --field-separator-zero -👎 set field separator for unaligned output to zero byte -👎 -0, --record-separator-zero -👎 set record separator for unaligned output to zero byte - -Connection options: -✅ -h, --host=HOSTNAME database server host or socket directory (default: "/var/run/postgresql") -✅ -p, --port=PORT database server port (default: "5432") -✅ -U, --username=USERNAME database user name (default: "ubuntu") -🤔 -w, --no-password never prompt for password -✅ -W, --password force password prompt (should happen automatically) - - -mysql - -Usage: mysql [OPTIONS] [database] -✅ -?, --help Display this help and exit. -👎 -I, --help Synonym for -? -✅ --auto-rehash Enable automatic rehashing. One doesn't need to use -✅ 'rehash' to get table and field completion, but startup -✅ and reconnecting may take a longer time. Disable with -✅ --disable-auto-rehash. -🤔 -A, --no-auto-rehash -🤔 No automatic rehashing. One has to use 'rehash' to get -🤔 table and field completion. This gives a quicker start of -🤔 mysql and disables rehashing on reconnect. WARNING: -🤔 options deprecated; use --disable-auto-rehash instead. -✅ -B, --batch Don't use history file. Disable interactive behavior. -✅ (Enables --silent) -👎 --character-sets-dir=name -👎 Directory where character sets are. -👎 --default-character-set=name -👎 Set the default character set. -✅ -C, --compress Use compression in server/client protocol. -🤔 -#, --debug[=#] This is a non-debug version. Catch this and exit -✅ -D, --database=name Database to use. -✅ --delimiter=name Delimiter to be used. -✅ -e, --execute=name Execute command and quit. (Disables --force and history -✅ file) -✅ -E, --vertical Print the output of a query (rows) vertically. -🤔 -f, --force Continue even if we get an sql error. -✅ -G, --named-commands -✅ Enable named commands. Named commands mean this program's -✅ internal commands; see mysql> help . When enabled, the -✅ named commands can be used from any line of the query, -✅ otherwise only from the first line, before an enter. -✅ Disable with --disable-named-commands. This option is -✅ disabled by default. -👎 -g, --no-named-commands -👎 Named commands are disabled. Use \* form only, or use -👎 named commands only in the beginning of a line ending -👎 with a semicolon (;) Since version 10.9 the client now -👎 starts with this option ENABLED by default! Disable with -👎 '-G'. Long format commands still work from the first -👎 line. WARNING: option deprecated; use -👎 --disable-named-commands instead. -👎 -i, --ignore-spaces Ignore space after function names. -👎 --local-infile Enable/disable LOAD DATA LOCAL INFILE. -👎 -b, --no-beep Turn off beep on error. -✅ -h, --host=name Connect to host. -✅ -H, --html Produce HTML output. -✅ -X, --xml Produce XML output -❌ --line-numbers Write line numbers for errors. -❌ -L, --skip-line-numbers -❌ Don't write line number for errors. WARNING: -L is -❌ deprecated, use long version of this option instead. -✅ -n, --unbuffered Flush buffer after each query. -✅ --column-names Write column names in results. -✅ -N, --skip-column-names -✅ Don't write column names in results. WARNING: -N is -✅ deprecated, use long version of this options instead. -🤔 -O, --set-variable=name -🤔 Change the value of a variable. Please note that this -🤔 option is deprecated; you can set variables directly with -🤔 --variable-name=value. -👎 --sigint-ignore Ignore SIGINT (CTRL-C) -❌ -o, --one-database Only update the default database. This is useful for -❌ skipping updates to other database in the update log. -👎 --pager[=name] Pager to use to display results. If you don't supply an -👎 option the default pager is taken from your ENV variable -👎 PAGER. Valid pagers are less, more, cat [> filename], -👎 etc. See interactive help (\h) also. This option does not -👎 work in batch mode. Disable with --disable-pager. This -👎 option is disabled by default. -👎 --no-pager Disable pager and print to stdout. See interactive help -👎 (\h) also. WARNING: option deprecated; use -👎 --disable-pager instead. -✅ -p, --password[=name] -✅ Password to use when connecting to server. If password is -✅ not given it's asked from the tty. -✅ -P, --port=# Port number to use for connection. -🤔 --prompt=name Set the mysql prompt to this value. -❌ --protocol=name The protocol of connection (tcp,socket,pipe,memory). -🤔 -q, --quick Don't cache result, print it row by row. This may slow -🤔 down the server if the output is suspended. Doesn't use -🤔 history file. -🤔 -r, --raw Write fields without conversion. Used with --batch. -🤔 --reconnect Reconnect if the connection is lost. Disable with -🤔 --disable-reconnect. This option is enabled by default. -✅ -s, --silent Be more silent. Print results with a tab as separator, -✅ each row on new line. -❌ -S, --socket=name Socket file to use for connection. -🤔 --ssl Enable SSL for connection (automatically enabled with -🤔 other flags). Disable with --skip-ssl. -🤔 --ssl-ca=name CA file in PEM format (check OpenSSL docs, implies -🤔 --ssl). -🤔 --ssl-capath=name CA directory (check OpenSSL docs, implies --ssl). -🤔 --ssl-cert=name X509 cert in PEM format (implies --ssl). -🤔 --ssl-cipher=name SSL cipher to use (implies --ssl). -🤔 --ssl-key=name X509 key in PEM format (implies --ssl). -🤔 --ssl-verify-server-cert -🤔 Verify server's "Common Name" in its cert against -🤔 hostname used when connecting. This option is disabled by -🤔 default. -✅ -t, --table Output in table format. -👎 -T, --debug-info Print some debug info at exit. -👎 --tee=name Append everything into outfile. See interactive help (\h) -👎 also. Does not work in batch mode. Disable with -👎 --disable-tee. This option is disabled by default. -👎 --no-tee Disable outfile. See interactive help (\h) also. WARNING: -👎 option deprecated; use --disable-tee instead -✅ -u, --user=name User for login if not current user. -❌ -U, --safe-updates Only allow UPDATE and DELETE that uses keys. -👎 -U, --i-am-a-dummy Synonym for option --safe-updates, -U. -🤔 -v, --verbose Write more. (-v -v -v gives the table output format). -✅ -V, --version Output version information and exit. -🤔 -w, --wait Wait and retry if connection is down. -✅ --connect_timeout=# Number of seconds before connection timeout. -🤔 --max_allowed_packet=# -🤔 Max packet length to send to, or receive from server -🤔 --net_buffer_length=# -🤔 Buffer for TCP/IP and socket communication -🤔 --select_limit=# Automatic limit for SELECT when using --safe-updates -❌ --max_join_size=# Automatic limit for rows in a join when using -❌ --safe-updates -❌ --secure-auth Refuse client connecting to server if it uses old -❌ (pre-4.1.1) protocol -🤔 --show-warnings Show warnings after every statement. \ No newline at end of file diff --git a/GO/cli/OutputFormatExamples.txt b/GO/cli/OutputFormatExamples.txt deleted file mode 100644 index e8f8386a..00000000 --- a/GO/cli/OutputFormatExamples.txt +++ /dev/null @@ -1,237 +0,0 @@ -SQLite3 - - sqlite3 -list test.db -SQLite version 3.35.4 2021-04-02 15:20:15 -Enter ".help" for usage hints. -sqlite> SELECT * FROM Dummy; -Andreas|Langholzstraße |91099 -Werner|Raiffeisenstr. 4|96145 - -# - -sqlite3 -csv test.db -SQLite version 3.35.4 2021-04-02 15:20:15 -Enter ".help" for usage hints. -sqlite> SELECT * FROM Dummy; -Andreas,"Langholzstraße ",91099 -Werner,"Raiffeisenstr. 4",96145 - -# - - sqlite3 -quote test.db -SQLite version 3.35.4 2021-04-02 15:20:15 -Enter ".help" for usage hints. -sqlite> SELECT * FROM Dummy; -'Andreas','Langholzstraße ',91099 -'Werner','Raiffeisenstr. 4',96145 - -# - - sqlite3 -tabs test.db -SQLite version 3.35.4 2021-04-02 15:20:15 -Enter ".help" for usage hints. -sqlite> SELECT * FROM Dummy; -Andreas Langholzstraße 91099 -Werner Raiffeisenstr. 4 96145 - -# - - sqlite3 -line test.db -SQLite version 3.35.4 2021-04-02 15:20:15 -Enter ".help" for usage hints. -sqlite> SELECT * FROM Dummy; - Names = Andreas -Address = Langholzstraße - ZIP = 91099 - - Names = Werner -Address = Raiffeisenstr. 4 - ZIP = 96145 - -# - - sqlite3 -json test.db -SQLite version 3.35.4 2021-04-02 15:20:15 -Enter ".help" for usage hints. -sqlite> SELECT * FROM Dummy; -[{"Names":"Andreas","Address":"Langholzstraße ","ZIP":91099}, -{"Names":"Werner","Address":"Raiffeisenstr. 4","ZIP":96145}] - -# - - sqlite3 -html test.db -SQLite version 3.35.4 2021-04-02 15:20:15 -Enter ".help" for usage hints. -sqlite> SELECT * FROM Dummy; -Andreas -Langholzstraße -91099 - -Werner -Raiffeisenstr. 4 -96145 - - -# - -sqlite3 -markdown test.db -SQLite version 3.35.4 2021-04-02 15:20:15 -Enter ".help" for usage hints. -sqlite> SELECT * FROM Dummy; -| Names | Address | ZIP | -|---------|------------------|-------| -| Andreas | Langholzstraße | 91099 | -| Werner | Raiffeisenstr. 4 | 96145 | - -# - -sqlite3 -table test.db -SQLite version 3.35.4 2021-04-02 15:20:15 -Enter ".help" for usage hints. -sqlite> SELECT * FROM Dummy; -+---------+------------------+-------+ -| Names | Address | ZIP | -+---------+------------------+-------+ -| Andreas | Langholzstraße | 91099 | -| Werner | Raiffeisenstr. 4 | 96145 | -+---------+------------------+-------+ - -# - - sqlite3 -box test.db -SQLite version 3.35.4 2021-04-02 15:20:15 -Enter ".help" for usage hints. -sqlite> SELECT * FROM Dummy; -┌─────────┬──────────────────┬───────┐ -│ Names │ Address │ ZIP │ -├─────────┼──────────────────┼───────┤ -│ Andreas │ Langholzstraße │ 91099 │ -│ Werner │ Raiffeisenstr. 4 │ 96145 │ -└─────────┴──────────────────┴───────┘ - -# - -sqlite3 -column test.db -SQLite version 3.35.4 2021-04-02 15:20:15 -Enter ".help" for usage hints. -sqlite> SELECT * FROM Dummy; -Andreas Langholzstraße 91099 -Werner Raiffeisenstr. 4 96145 - - -mysql --xml - - - - 129 - 107 - Eastern - - - - - - - - -res.DumpToWriter( out, sqlitecloud.OUTFORMAT_LIST, "|", 0, false ) -109|Some|One|96450|Coburg|Mohrenstrasse 1 -110|Someone|Else|96145|Sesslach|Raiffeisenstrasse 6 -111|One|More|91099|Poxdorf|Langholzstr. 4 - -res.DumpToWriter( out, sqlitecloud.OUTFORMAT_CSV, "|", 0, false ) -109,Some,One,96450,Coburg,"Mohrenstrasse 1" -110,Someone,Else,96145,Sesslach,"Raiffeisenstrasse 6" -111,One,More,91099,Poxdorf,"Langholzstr. 4" - -res.DumpToWriter( out, sqlitecloud.OUTFORMAT_QUOTE, "|", 0, false ) -109,'Some','One',96450,'Coburg','Mohrenstrasse 1' -110,'Someone','Else',96145,'Sesslach','Raiffeisenstrasse 6' -111,'One','More',91099,'Poxdorf','Langholzstr. 4' - -res.DumpToWriter( out, sqlitecloud.OUTFORMAT_TABS, "|", 0, false ) -109 Some One 96450 Coburg Mohrenstrasse 1 -110 Someone Else 96145 Sesslach Raiffeisenstrasse 6 -111 One More 91099 Poxdorf Langholzstr. 4 - -res.DumpToWriter( out, sqlitecloud.OUTFORMAT_LINE, "|", 0, false ) - ID = 112 -FirstName = Some - LastName = One - ZIP = 96450 - City = Coburg - Address = Mohrenstrasse 1 - - ID = 113 -FirstName = Someone - LastName = Else - ZIP = 96145 - City = Sesslach - Address = Raiffeisenstrasse 6 - - ID = 114 -FirstName = One - LastName = More - ZIP = 91099 - City = Poxdorf - Address = Langholzstr. 4 - -res.DumpToWriter( out, sqlitecloud.OUTFORMAT_JSON, "|", 0, false ) -[ - {"ID":"115","FirstName":"Some","LastName":"One","ZIP":"96450","City":"Coburg","Address":"Mohrenstrasse 1"}, - {"ID":"116","FirstName":"Someone","LastName":"Else","ZIP":"96145","City":"Sesslach","Address":"Raiffeisenstrasse 6"}, - {"ID":"117","FirstName":"One","LastName":"More","ZIP":"91099","City":"Poxdorf","Address":"Langholzstr. 4"}, -] - -res.DumpToWriter( out, sqlitecloud.OUTFORMAT_HTML, "|", 0, false ) - - 115 - Some - One - 96450 - Coburg - Mohrenstrasse 1 - - - 116 - Someone - Else - 96145 - Sesslach - Raiffeisenstrasse 6 - - - 117 - One - More - 91099 - Poxdorf - Langholzstr. 4 - - -res.DumpToWriter( out, sqlitecloud.OUTFORMAT_MARKDOWN, "|", 0, false ) -| ID | FirstName | LastName | ZIP | City | Address | -|-----|-----------|----------|-------|----------|---------------------| -| 115 | Some | One | 96450 | Coburg | Mohrenstrasse 1 | -| 116 | Someone | Else | 96145 | Sesslach | Raiffeisenstrasse 6 | -| 117 | One | More | 91099 | Poxdorf | Langholzstr. 4 | - -res.DumpToWriter( out, sqlitecloud.OUTFORMAT_TABLE, "|", 0, false ) -+-----+-----------+----------+-------+----------+---------------------+ -| ID | FirstName | LastName | ZIP | City | Address | -+-----+-----------+----------+-------+----------+---------------------+ -| 115 | Some | One | 96450 | Coburg | Mohrenstrasse 1 | -| 116 | Someone | Else | 96145 | Sesslach | Raiffeisenstrasse 6 | -| 117 | One | More | 91099 | Poxdorf | Langholzstr. 4 | -+-----+-----------+----------+-------+----------+---------------------+ - -res.DumpToWriter( out, sqlitecloud.OUTFORMAT_BOX, "|", 0, false ) -┌─────┬───────────┬──────────┬───────┬──────────┬─────────────────────┐ -│ ID │ FirstName │ LastName │ ZIP │ City │ Address │ -├─────┼───────────┼──────────┼───────┼──────────┼─────────────────────┤ -│ 115 │ Some │ One │ 96450 │ Coburg │ Mohrenstrasse 1 │ -│ 116 │ Someone │ Else │ 96145 │ Sesslach │ Raiffeisenstrasse 6 │ -│ 117 │ One │ More │ 91099 │ Poxdorf │ Langholzstr. 4 │ -└─────┴───────────┴──────────┴───────┴──────────┴─────────────────────┘ - -res.DumpToWriter( out, sqlitecloud.OUTFORMAT_XML, "|", 0, false ) \ No newline at end of file diff --git a/GO/cli/README.md b/GO/cli/README.md deleted file mode 100644 index 7112c36b..00000000 --- a/GO/cli/README.md +++ /dev/null @@ -1,475 +0,0 @@ -# SQLite Cloud Command Line Client - -## Main Features -- Many command line arguments for good feature compatibility with other mayor database systems -- Connection Strings -- Batch and interactive mode -- Can read sql scripts from multible files and stdin simultaniously -- Many result output formats -- Many internal commands (.dot commands) for good feature compatibility with other mayor database systems -- Automatic line trunctions for nice terminal rendering -- Automatic rendering of numbers and size/time units -- Command history -- Static and dynamic SQL qutocomplete - -## Compatibility with other database command line clients - -
-✅ = Implemented, directly or indrectly available
-🤔 = Not Implemented / Maybe later?
-👎 = Decided agains implementation, not usefull
-❌ = Does not apply / will never be implemented
-
- -### Command line compatibility with sqlite3 - -
-🤔   -A ARGS...             run ".archive ARGS" and exit
-❌   -append                append the database to the end of the file
-👎   -ascii                 set output mode to 'ascii'
-✅   -bail                  stop after hitting an error
-🤔   -batch                 force batch I/O
-✅   -box                   set output mode to 'box'
-✅   -column                set output mode to 'column'
-✅   -cmd COMMAND           run "COMMAND" before reading stdin
-✅   -csv                   set output mode to 'csv'
-❌   -deserialize           open the database using sqlite3_deserialize()
-✅   -echo                  print commands before execution
-✅   -init FILENAME         read/process named file
-✅   -[no]header            turn headers on or off
-✅   -help                  show this message
-✅   -html                  set output mode to HTML
-👎   -interactive           force interactive I/O
-✅   -json                  set output mode to 'json'
-✅   -line                  set output mode to 'line'
-✅   -list                  set output mode to 'list'
-❌   -lookaside SIZE N      use N entries of SZ bytes for lookaside memory
-✅   -markdown              set output mode to 'markdown'
-❌   -maxsize N             maximum size for a --deserialize database
-❌   -memtrace              trace all memory allocations and deallocations
-❌   -mmap N                default mmap size set to N
-✅   -newline SEP           set output row separator. Default: '\n'
-❌   -nofollow              refuse to open symbolic links to database files
-✅   -nullvalue TEXT        set text string for NULL values. Default ''
-❌   -pagecache SIZE N      use N slots of SZ bytes each for page cache memory
-✅   -quote                 set output mode to 'quote'
-❌   -readonly              open the database read-only
-✅   -separator SEP         set output column separator. Default: '|'
-👎   -stats                 print memory stats before each finalize
-✅   -table                 set output mode to 'table'
-✅   -tabs                  set output mode to 'tabs'
-✅   -version               show SQLite version
-❌   -vfs NAME              use NAME as the default VFS
-❌   -zip                   open the file as a ZIP Archive
-
- -### Internal command compatibility with sqlite3 - -
-🤔 .archive ...             Manage SQL archives
-🤔 .auth ON|OFF             Show authorizer callbacks
-🤔 .backup ?DB? FILE        Backup DB (default "main") to FILE
-✅ .bail on|off             Stop after hitting an error.  Default OFF
-👎 .binary on|off           Turn binary output on or off.  Default OFF
-🤔 .cd DIRECTORY            Change the working directory to DIRECTORY
-🤔 .changes on|off          Show number of rows changed by SQL
-🤔 .check GLOB              Fail if output since .testcase does not match
-🤔 .clone NEWDB             Clone data into NEWDB from the existing database
-👎 .databases               List names and files of attached databases
-👎 .dbconfig ?op? ?val?     List or change sqlite3_db_config() options
-🤔 .dbinfo ?DB?             Show status information about the database
-🤔 .dump ?OBJECTS?          Render database content as SQL
-✅ .echo on|off             Turn command echo on or off
-👎 .eqp on|off|full|...     Enable or disable automatic EXPLAIN QUERY PLAN
-👎 .excel                   Display the output of next command in spreadsheet
-✅ .exit ?CODE?             Exit this program with return-code CODE
-🤔 .expert                  EXPERIMENTAL. Suggest indexes for queries
-🤔 .explain ?on|off|auto?   Change the EXPLAIN formatting mode.  Default: auto
-🤔 .filectrl CMD ...        Run various sqlite3_file_control() operations
-🤔 .fullschema ?--indent?   Show schema and the content of sqlite_stat tables
-✅ .headers on|off          Turn display of headers on or off
-✅ .help ?-all? ?PATTERN?   Show help text for PATTERN
-🤔 .import FILE TABLE       Import data from FILE into TABLE
-🤔 .imposter INDEX TABLE    Create imposter table TABLE on index INDEX
-🤔 .indexes ?TABLE?         Show names of indexes
-🤔 .limit ?LIMIT? ?VAL?     Display or change the value of an SQLITE_LIMIT
-🤔 .lint OPTIONS            Report potential schema issues.
-❌ .load FILE ?ENTRY?       Load an extension library
-🤔 .log FILE|off            Turn logging on or off.  FILE can be stderr/stdout
-✅ .mode MODE ?TABLE?       Set output mode
-✅ .nullvalue STRING        Use STRING in place of NULL values
-👎 .once ?OPTIONS? ?FILE?   Output for the next SQL command only to FILE
-🤔 .open ?OPTIONS? ?FILE?   Close existing database and reopen FILE
-🤔 .output ?FILE?           Send output to FILE or stdout if FILE is omitted
-🤔 .parameter CMD ...       Manage SQL parameter bindings
-🤔 .print STRING...         Print literal STRING
-🤔 .progress N              Invoke progress handler after every N opcodes
-🤔 .prompt MAIN CONTINUE    Replace the standard prompts
-✅ .quit                    Exit this program
-🤔 .read FILE               Read input from FILE
-❌ .recover                 Recover as much data as possible from corrupt db.
-🤔 .restore ?DB? FILE       Restore content of DB (default "main") from FILE
-❌ .save FILE               Write in-memory database into FILE
-🤔 .scanstats on|off        Turn sqlite3_stmt_scanstatus() metrics on or off
-🤔 .schema ?PATTERN?        Show the CREATE statements matching PATTERN
-❌ .selftest ?OPTIONS?      Run tests defined in the SELFTEST table
-✅ .separator COL ?ROW?     Change the column and row separators
-👎 .session ?NAME? CMD ...  Create or control sessions
-❌ .sha3sum ...             Compute a SHA3 hash of database content
-🤔 .shell CMD ARGS...       Run CMD ARGS... in a system shell
-🤔 .show                    Show the current values for various settings
-🤔 .stats ?ARG?             Show stats or turn stats on or off
-🤔 .system CMD ARGS...      Run CMD ARGS... in a system shell
-👎 .tables ?TABLE?          List names of tables matching LIKE pattern TABLE
-❌ .testcase NAME           Begin redirecting output to 'testcase-out.txt'
-❌ .testctrl CMD ...        Run various sqlite3_test_control() operations
-✅ .timeout MS              Try opening locked tables for MS milliseconds
-🤔 .timer on|off            Turn SQL timer on or off
-🤔 .trace ?OPTIONS?         Output each SQL statement as it is run
-❌ .vfsinfo ?AUX?           Information about the top-level VFS
-❌ .vfslist                 List all available VFSes
-❌ .vfsname ?AUX?           Print the name of the VFS stack
-🤔 .width NUM1 NUM2 ...     Set minimum column widths for columnar output
-
- -## Getting started - -### Compile -```console -go env -w GO111MODULE=off -cd sdk/GO -export GOPATH=`pwd` -echo $GOPATH -make cli - -``` - -### Usage -```console -./bin/sqlc --help -SQLite Cloud Command Line Application Command Line Interface. - -Usage: - sqlc [URL] [options] [...] - sqlc -?|--help|--version - -Arguments: - URL "sqlitecloud://user:pass@host.com:port/dbname?timeout=10&compress=NO" - FILE... Execute SQL commands from FILE(s) after connecting to the SQLite Cloud database - -Examples: - sqlc "sqlitecloud://user:pass@host.com:8860/dbname?timeout=10&compress=lz4" - sqlc --host hostname -u user --password=pass -d dbname -c LZ4 - sqlc --version - sqlc -? - -General Options: - --cmd COMMAND Run "COMMAND" before executing FILE... or reading from stdin - -l, --list List available databases, then exit - -d, --dbname NAME Use database NAME - -b, --bail Stop after hitting an error - -?, --help Show this screen - --version Display version information - -Output Format Options: - -o, --output FILE Switch to BATCH mode, execute SQL Commands and send output to FILE, then exit. - In BATCH mode, the default output format is switched to QUOTE. - - --echo Disables --quiet, print command(s) before execution - --quiet Disables --echo, run command(s) quietly (no messages, only query output) - --noheader Turn headers off - --nullvalue TEXT Set text string for NULL values [default: "NULL"] - --newline SEP Set output row separator [default: "\r\n"] - --separator SEP Set output column separator [default: "|"] - --format (LIST|CSV|QUOTE|TABS|LINE|JSON|HTML|XML|MARKDOWN|TABLE|BOX) - Specify the Output mode [default: BOX] - -Connection Options: - -h, --host HOSTNAME Connect to SQLite Cloud database server host name [default::localhost] - -p, --port PORT Use specified port to connect to SQLIte Cloud database server [default::8860] - -u, --user USERNAME Use USERNAME for authentication - -w, --password PASSWORD Use PASSWORD for authentication - -t, --timeout SECS Set Timeout for network operations to SECS seconds [default::10] - -c, --compress (NO|LZ4) Use line compression [default::NO] - --tls [YES|NO|INTERN|FILE] Encrypt the database connection using the host's root CA set (YES), a custom CA with a PEM from FILE (FILE), the internal SQLiteCloud CA (INTERN), or disable the encryption (NO) [default::YES] - -``` - -### Internal Commands -```console - hostname.com:X > .help - -.help Show this message -.bail [on|off] Stop after hitting an error [default: off] -.echo [on|off] Print command(s) before execution [default: off] -.quiet [on|off] Run command(s) quietly (no messages, only query output) [default: on] -.noheader [on|off] Turn table headers off or on [default: off] -.nullvalue TEXT Set TEXT string for NULL values [default: "NULL"] -.newline TEXT Set output row separator [default: "\r\n"] -.separator TEXT Set output column separator [default: ""] -.format [LIST|CSV|QUOTE|TABS|LINE|JSON|HTML|XML|MARKDOWN|TABLE|BOX] - Specify the Output mode [default: BOX] -.width [-1|0|] Sets the maximum allowed query result length per line to the - terminal width(-1), unlimited (0) or any other width() [default: -1] -.timeout Set Timeout for network operations to SECS seconds [default: 10] -.compress Use line compression [default: NO] -.exit, .quit Exit this program - -If no parameter is specified, then the default value is used as the parameter value. -Boolean settings are toggled if no parameter is specified. - -hostname:X > - -``` - -## Using the CLI - -### Starting a new session -```console -./bin/sqlc --host=hostname --dbname=X --tls=INTERN - _____ - / / SQLite Cloud Command Line Application, version 1.0.1 - / ___/ / (c) 2021 by SQLite Cloud Inc. - \ ___/ / - \_ ___/ Enter ".help" for usage hints. - -hostname:X > - -``` - -### SELECT'ing some data -```console -hostname:X > SELECT * FROM Dummy; -┌─────┬───────────┬──────────┬───────┬──────────┬─────────────────────┐ -│ ID │ FirstName │ LastName │ ZIP │ City │ Address │ -├─────┼───────────┼──────────┼───────┼──────────┼─────────────────────┤ -│ 369 │ Some │ One │ 96450 │ Coburg │ Mohrenstraße 1 │ -│ 370 │ Someone │ Else │ 96145 │ Sesslach │ Raiffeisenstraße 6 │ -│ 371 │ One │ More │ 91099 │ Poxdorf │ Langholzstr. 4 │ -│ 372 │ Quotation │ Test │ 12345 │ &"<> │ 'Straße 0' │ -└─────┴───────────┴──────────┴───────┴──────────┴─────────────────────┘ -Rows: 4 - Cols: 6: 282 Bytes Time: 86.43071ms - -hostname:X > - -``` -### DELETE'ing a row -```console -hostname:X > DELETE FROM Dummy WHERE ID = 372; -OK - -hostname:X > - -``` - -### Changing the outformat -```console -hostname:X > .format xml -hostname:X > SELECT * FROM Dummy; - - - - 369 - Some - One - 96450 - Coburg - Mohrenstraße 1 - - - 370 - Someone - Else - 96145 - Sesslach - Raiffeisenstraße 6 - - - 371 - One - More - 91099 - Poxdorf - Langholzstr. 4 - - -Rows: 3 - Cols: 6: 229 Bytes Time: 82.762014ms - -hostname:X > - -``` -### Changing the outformat back -One can enter `.format` without any argument to switch the output format back to its default format or one could enter an explicit format like `.format box`. - -### Line Truncation explained: -Lets assume, that you have a narrow terminal window. If you have entered the following commmands, the output would look something like this: -```console -hostname:X > .format table - hostname:X > SELECT * FROM Dummy; -+-----+-----------+----------+-------+----------+--------------- -------+ -| ID | FirstName | LastName | ZIP | City | Address - | -+-----+-----------+----------+-------+----------+--------------- -------+ -| 369 | Some | One | 96450 | Coburg | Mohrenstraße 1 - | -| 370 | Someone | Else | 96145 | Sesslach | Raiffeisenstra -ße 6 | -| 371 | One | More | 91099 | Poxdorf | Langholzstr. 4 - | -+-----+-----------+----------+-------+----------+--------------- -------+ -Rows: 3 - Cols: 6: 229 Bytes Time: 81.418646ms - -hostname:X > .format json -hostname:X > SELECT * FROM Dummy; - -[ - {"ID":369,"FirstName":"Some","LastName":"One","ZIP":96450,"Cit -y":"Coburg","Address":"Mohrenstraße 1",}, - {"ID":370,"FirstName":"Someone","LastName":"Else","ZIP":96145, -"City":"Sesslach","Address":"Raiffeisenstraße 6",}, - {"ID":371,"FirstName":"One","LastName":"More","ZIP":91099,"Cit -y":"Poxdorf","Address":"Langholzstr. 4",}, -] -Rows: 3 - Cols: 6: 229 Bytes Time: 87.014696ms - -hostname:X > - -``` -You can see a nasty line break in the middle of the result line that can easily ruin the screen reading experience. To avoid this annoyance, sqlc build in line trucation mechanism trims its output line in a terminal session by default. The result looks like this: -```console -hostname:X > .format table -hostname:X > SELECT * FROM Dummy; -+-----+-----------+----------+-------+----------+--------------… -| ID | FirstName | LastName | ZIP | City | Address… -+-----+-----------+----------+-------+----------+--------------… -| 369 | Some | One | 96450 | Coburg | Mohrenstraße … -| 370 | Someone | Else | 96145 | Sesslach | Raiffeisenstr… -| 371 | One | More | 91099 | Poxdorf | Langholzstr. … -+-----+-----------+----------+-------+----------+--------------… -Rows: 3 - Cols: 6: 229 Bytes Time: 84.409225ms - -hostname:X > .format json -hostname:X > SELECT * FROM Dummy; -[ - {"ID":369,"FirstName":"Some","LastName":"One","ZIP":96450,"Ci… - {"ID":370,"FirstName":"Someone","LastName":"Else","ZIP":96145… - {"ID":371,"FirstName":"One","LastName":"More","ZIP":91099,"Ci… -] -Rows: 3 - Cols: 6: 229 Bytes Time: 88.874433ms - -hostname:X > -``` - -If an output line was trimed to a certain width, the truncation can easily be spoted by the `…` character at the very end of a line. In batch mode, all output is sent to an output file, no line truncation will occure. You can switch off this autotrunction behaviour with a `.width 0` command. To switch back to auto truncation, use `.width -1`. Truncation to any other width is also possible with, for exampel a `.width 35` command. - -### Using Autocomplete -To use the build in autocomplete feature, use the [TAB] key. The [TAB] key will try to guess what SQL command you was trying to use and autocomplete this SQL command for you. If autocomplete has guessed the wrong command, keep pressing [TAB] until the right command shows up. The Autocomplete knows all available SQLite Cloud server and SQLite Cloud SQL commands and functions. If you have selected a database (`USE DATABASE ...`), autocomplete will also help you with the Table and Colum names. "UPDATING'ing some data" shows a simple example session: - -### UPDATING'ing some data -```console -hostname:X > sel[TAB] -hostname:X > SELECT -hostname:X > SELECT Fi[TAB] -hostname:X > SELECT FirstName -hostname:X > SELECT FirstName, Dum[TAB][TAB][TAB][TAB] -hostname:X > SELECT FirstName, Dummy.LastName -hostname:X > SELECT FirstName, Dummy.LastName Fr[TAB] -hostname:X > SELECT FirstName, Dummy.LastName FROM D[TAB] -hostname:X > SELECT FirstName, Dummy.LastName FROM Dummy[RETURN] -┌───────────┬──────────┐ -│ FirstName │ LastName │ -├───────────┼──────────┤ -│ Some │ One │ -│ Someone │ Else │ -│ One │ More │ -└───────────┴──────────┘ -Rows: 3 - Cols: 2: 74 Bytes Time: 81.865386ms - -hostname:X > up[TAB] -hostname:X > UPDATE D[TAB] -hostname:X > UPDATE Dummy SET La[TAB] -hostname:X > UPDATE Dummy SET LastName -hostname:X > UPDATE Dummy SET LastName = "ONE" WH[TAB] -hostname:X > UPDATE Dummy SET LastName = "ONE" WHERE id=369[RETURN] -OK - -hostname:X > SELECT * FROM Dummy; -┌─────┬───────────┬──────────┬───────┬──────────┬─────────────────────┐ -│ ID │ FirstName │ LastName │ ZIP │ City │ Address │ -├─────┼───────────┼──────────┼───────┼──────────┼─────────────────────┤ -│ 369 │ Some │ ONE │ 96450 │ Coburg │ Mohrenstraße 1 │ -│ 370 │ Someone │ Else │ 96145 │ Sesslach │ Raiffeisenstraße 6 │ -│ 371 │ One │ More │ 91099 │ Poxdorf │ Langholzstr. 4 │ -└─────┴───────────┴──────────┴───────┴──────────┴─────────────────────┘ -Rows: 3 - Cols: 6: 229 Bytes Time: 82.797135ms - -hostname:X > -``` - -### Setting the prompt -One can set the user promt ether with the `--promt ` command line switch or within the app with a `.promt `. The follwoing format strings will automatically be replaced by it's actual values: - -(https://wiki.ubuntuusers.de/Bash/Prompt/) - -| Format String | Meaning | Example replacement | -|:---|:---|:---| -| \H | Database Host name | hostname | -| \p | Database Port | 8860 | -| \u | Username | marco | -| \d | Database name | X | -| \ | Actual Date | 2021-10-08 | -| \t | Actual Time | 14:37:32 | -| \w | Local path | ~ | - -The following prebuild patterns are available as a shortcut: - -| Shortcut | Definition | Example | -|:---|:---|:---| -| default | ``$host$:$dbname$ >`` | hostname:X >_ | -| simple | ``sqlc >`` | sqlc >_ | -| full | ``$host$:$dbname$ $user$> `` | hostname:X username> _ | - -### Using Synthax Highlighting -One can switch synthax highlighting on with ``.synthax color``. There is also a monocrome synthax highlighting mode available that is VT100 compatible and uses only bold and italic character representations. You can switch into this simple VT100 mode with ``.synthax vt100``. To switch synthay highlighting off, please enter ``.synthax off``. Synthax highlighting is set to color in interactive mode by default. Synthay highlighting is automatically switched off in batch mode. - - -### Exiting the app -```console -hostname:X > .exit - -``` - -## Testing -```console -./bin/sqlc -? -./bin/sqlc --help -./bin/sqlc --version - -./bin/sqlc -> trying to connect -./bin/sqlc sqlitecloud://hostname/X?tls=INTERN -> trying to connect - -./bin/sqlc sqlitecloud://hostname/X?tls=INTERN - -./bin/sqlc sqlitecloud://hostname/X?tls=INTERN --list -./bin/sqlc sqlitecloud://hostname/X?tls=INTERN --list --format=xml -./bin/sqlc sqlitecloud://hostname/X?tls=INTERN --cmd "LIST DATABASES" -echo "LIST DATABASES" | ./bin/sqlc sqlitecloud://hostname/X?tls=INTERN --format=json -echo "LIST DATABASES" > script.sql; ./bin/sqlc sqlitecloud://hostname/X?tls=INTERN --format=json script.sql - -./bin/sqlc sqlitecloud://hostname/X?tls=INTERN --list -o outputfile --quiet --format=xml - -``` - -## ToDos -- [ ] --promt -- [ ] --reconnect -- [x] When started without a host, localhost is assumed. If no database server is running on the localhost, a 10 second timeout will occure. -- [ ] Make internal .dot commands available in batch files -- [ ] Test for "empty commands" -- [ ] Add --log feature -- [x] Remove the table "sqlite_sequence" from dynamic autocomplete scanning -- [ ] Implement the Auth command to use the Password feature -- [ ] Add more Test example commands \ No newline at end of file diff --git a/GO/cli/go.mod b/GO/cli/go.mod deleted file mode 100644 index bc877f87..00000000 --- a/GO/cli/go.mod +++ /dev/null @@ -1,22 +0,0 @@ -module github.com/sqlitecloud/sdk/go/cli - -go 1.18 - -require ( - github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 - github.com/peterh/liner v1.2.2 - golang.org/x/term v0.1.0 -) - -require ( - github.com/google/go-cmp v0.5.9 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/mattn/go-runewidth v0.0.3 // indirect - github.com/pierrec/lz4 v2.6.1+incompatible // indirect - github.com/xo/dburl v0.12.4 // indirect - golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 // indirect -) - -require github.com/sqlitecloud/sdk v0.0.0 - -replace github.com/sqlitecloud/sdk v0.0.0 => ../sdk diff --git a/GO/cli/go.sum b/GO/cli/go.sum deleted file mode 100644 index 05ef80e7..00000000 --- a/GO/cli/go.sum +++ /dev/null @@ -1,25 +0,0 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw= -github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI= -github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= -github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/xo/dburl v0.12.4 h1:mAIQjCNqCRtfytZNN0tZzK01rfng3n4Ei1s+H9lh61I= -github.com/xo/dburl v0.12.4/go.mod h1:K6rSPgbVqP3ZFT0RHkdg/M3M5KhLeV2MaS/ZqaLd1kA= -golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI= -golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/GO/cli/script.sql b/GO/cli/script.sql deleted file mode 100644 index e39e6585..00000000 --- a/GO/cli/script.sql +++ /dev/null @@ -1,4 +0,0 @@ -LIST DATABASES -PING -LIST COMMANDS - diff --git a/GO/cli/sqlc.go b/GO/cli/sqlc.go deleted file mode 100644 index 93b736f6..00000000 --- a/GO/cli/sqlc.go +++ /dev/null @@ -1,692 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud CLI Application -// /// /// /// Version : 1.1.1 -// // /// /// /// Date : 2021/10/08 -// /// /// /// /// Author : Andreas Pfeil -// /// /// /// /// -// /// ////////// /// /// Description : Features: Connection Strings, -// //// /// /// Batch processing, Many output -// //// ////////// /// formats, Line truncation for Terminals, -// //// //// History, Static & Dynamic Autocomplete -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -package main - -import ( - sqlitecloud "github.com/sqlitecloud/sdk" - - "bufio" - "errors" - "fmt" - "io" - "os" - "reflect" - "strconv" - "strings" - "time" - - "github.com/docopt/docopt-go" - "github.com/peterh/liner" - "golang.org/x/term" -) - -var app_name = "sqlc" -var long_name = "SQLite Cloud Command Line Application" -var version = "version 1.1.1" -var copyright = "(c) 2021 by SQLite Cloud Inc." -var history_file = fmt.Sprintf("~/.%s_history.txt", app_name) - -var banner = fmt.Sprintf(` _____ - / / %s, %s - / ___/ / %s - \ ___/ / - \_ ___/ Enter ".help" for usage hints.`, long_name, version, copyright) - -var usage = long_name + ` Command Line Interface. - -Usage: - sqlc [URL] [options] [...] - sqlc -?|--help|--version - -Arguments: - URL "sqlitecloud://user:pass@host.com:port/dbname?timeout=10&compress=NO" - FILE... Execute SQL commands from FILE(s) after connecting to the SQLite Cloud database - -Examples: - sqlc "sqlitecloud://user:pass@host.com:8860/dbname?timeout=10&compress=lz4&tls=intern" - sqlc --host hostname -u user --password=pass -d dbname -c LZ4 --tls=no - sqlc --version - sqlc -? - -General Options: - --cmd COMMAND Run "COMMAND" before executing FILE... or reading from stdin - -l, --list List available databases, then exit - -d, --dbname NAME Use database NAME - -b, --bail Stop after hitting an error - -?, --help Show this screen - --version Display version information - -Output Format Options: - -o, --output FILE Switch to BATCH mode, execute SQL Commands and send output to FILE, then exit. - In BATCH mode, the default output format is switched to QUOTE. - - --echo Disables --quiet, print command(s) before execution - --quiet Disables --echo, run command(s) quietly (no messages, only query output) - --noheader Turn headers off - --nullvalue TEXT Set text string for NULL values [default: "NULL"] - --newline SEP Set output row separator [default: "\r\n"] - --separator SEP Set output column separator [default::"|"] - --format (LIST|CSV|QUOTE|TABS|LINE|JSON|HTML|XML|MARKDOWN|TABLE|BOX) - Specify the Output mode [default::BOX] - -Connection Options: - -h, --host HOSTNAME Connect to SQLite Cloud database server host name [default::localhost] - -p, --port PORT Use specified port to connect to SQLIte Cloud database server [default::8860] - -u, --user USERNAME Use USERNAME for authentication - -w, --password PASSWORD Use PASSWORD for authentication - -t, --timeout SECS Set Timeout for network operations to SECS seconds [default::10] - -c, --compress (NO|LZ4) Use line compression [default::NO] - --tls [YES|NO|INTERN|FILE] Encrypt the database connection using the host's root CA set (YES), a custom CA with a PEM from FILE (FILE), the internal SQLiteCloud CA (INTERN), or disable the encryption (NO) [default::YES] -` - -var help = ` -.help Show this message -.bail [on|off] Stop after hitting an error [default: off] -.echo [on|off] Print command(s) before execution [default: off] -.quiet [on|off] Run command(s) quietly (no messages, only query output) [default: on] -.noheader [on|off] Turn table headers off or on [default: off] -.nullvalue TEXT Set TEXT string for NULL values [default: "NULL"] -.newline TEXT Set output row separator [default: "\r\n"] -.separator TEXT Set output column separator [default: ""] -.format [LIST|CSV|QUOTE|TABS|LINE|JSON|HTML|XML|MARKDOWN|TABLE|BOX] - Specify the Output mode [default: BOX] -.width [-1|0|] Sets the maximum allowed query result length per line to the - terminal width(-1), unlimited (0) or any other width() [default: -1] -.timeout Set Timeout for network operations to SECS seconds [default: 10] -.compress Use line compression [default: NO] -.exit, .quit Exit this program - -If no parameter is specified, then the default value is used as the parameter value. -Boolean settings are toggled if no parameter is specified. -` - -type Parameter struct { - URL string `docopt:"URL"` - - OutFile string `docopt:"--output"` - Command string `docopt:"--cmd"` - List bool `docopt:"--list"` - Bail bool `docopt:"--bail"` - Echo bool `docopt:"--echo"` - Quiet bool `docopt:"--quiet"` - NoHeader bool `docopt:"--noheader"` - NullText string `docopt:"--nullvalue"` - NewLine string `docopt:"--newline"` - Separator string `docopt:"--separator"` - Format string `docopt:"--format"` - OutPutFormat int `docopt:"--outputformat"` - - Host string `docopt:"--host"` - Port int `docopt:"--port"` - User string `docopt:"--user"` - Password string `docopt:"--password"` - Database string `docopt:"--dbname"` - ApiKey string `docopt:"--apikey"` - NoBlob bool `docopt:"--noblob"` - MaxData int `docopt:"--maxdata"` - MaxRows int `docopt:"--maxrows"` - MaxRowset int `docopt:"--maxrowset"` - - Timeout int `docopt:"--timeout"` - Compress string `docopt:"--compress"` - Tls string `docopt:"--tls"` - UseStdIn bool `docopt:"-"` - Files []string `docopt:""` -} - -var tokens = []string{".echo ", ".help ", ".bail ", ".quiet ", ".noheader ", ".nullvalue ", ".newline ", ".separator ", ".format ", - ".width ", ".quit ", - "SELECT ", "AS ", "FROM ", "JOIN ", "ON ", "USING ", "WHERE ", "LIKE ", "OR ", "AND ", "GROUP ", "ORDER ", "BY ", "ASC ", "DESC ", "LIMIT ", "TO ", - "INSERT ", "UPDATE ", "DROP ", "IF ", "NOT ", "EXISTS ", "FAIL ", "IGNORE ", "TABLE ", "VALUES ", "SET ", "INTO ", - "CREATE ", "ALTER ", "NULL ", "INTEGER ", "TEXT ", "PRIMARY ", "UNIQUE ", "DEFAULT ", - "ABORT ", "ACTION ", "AFTER ", "ALL ", "ALWAYS ", "ANALYZE ", "ADD ", "ATTACH ", - "AUTOINCREMENT ", "BEFORE ", "BEGIN ", "BETWEEN ", "CASCADE ", "CASE ", "CAST ", "CHECK ", "COLLATE ", "COLUMN ", - "COMMIT ", "CONFLICT ", "CONSTRAINT ", "CROSS ", "CURRENT ", "CURRENT_DATE ", "CURRENT_TIME ", - "CURRENT_TIMESTAMP ", "DATABASE ", "DEFERRABLE ", "DEFERRED ", "DELETE ", "DETACH ", "DISTINCT ", - "DO ", "EACH ", "ELSE ", "END ", "ESCAPE ", "EXCEPT ", "EXCLUDE ", "EXCLUSIVE ", "EXPLAIN ", - "FILTER ", "FIRST ", "FOLLOWING ", "FOR ", "FOREIGN ", "FULL ", "GENERATED ", "GLOB ", "GROUPS ", - "HAVING ", "IMMEDIATE ", "IN ", "INDEX ", "INDEXED ", "INITIALLY ", "INNER ", "INSTEAD ", - "INTERSECT ", "IS ", "ISNULL ", "KEY ", "LAST ", "LEFT ", "MATCH ", "MATERIALIZED ", - "NATURAL ", "NO ", "NOTHING ", "NOTNULL ", "NULLS ", "OF ", "OFFSET ", - "OTHERS ", "OUTER ", "OVER ", "PARTITION ", "PLAN ", "PRAGMA ", "PRECEDING ", "QUERY ", "RAISE ", "RANGE ", - "RECURSIVE ", "REFERENCES ", "REGEXP ", "REINDEX ", "RELEASE ", "RENAME ", "REPLACE ", "RESTRICT ", "RETURNING ", - "RIGHT ", "ROLLBACK ", "ROW ", "ROWS ", "SAVEPOINT ", "TEMP ", "TEMPORARY ", "THEN ", - "TIES ", "TRANSACTION ", "TRIGGER ", "UNBOUNDED ", "UNION ", "VACUUM ", - "VIEW ", "VIRTUAL ", "WHEN ", "WINDOW ", "WITH ", "WITHOUT ", - "ABS( ", "CHANGES( ", "CHAR( ", "COALESCE( ", "GLOB( ", "HEX( ", "IFNULL( ", "IIF( ", "INSTR( ", "LAST_INSERT_ROWID( ", - "LENGTH( ", "LIKE( ", "LIKELIHOOD( ", "LIKELY( ", "LOAD_EXTENSION( ", "LOWER( ", "LTRIM( ", "MAX( ", "MIN( ", - "NULLIF( ", "PRINTF( ", "QUOTE( ", "RANDOM() ", "RANDOMBLOB( ", "REPLACE( ", "ROUND( ", "RTRIM( ", "SIGN( ", - "SOUNDEX( ", "SQLITE_COMPILEOPTION_GET( ", "SQLITE_COMPILEOPTION_USED( ", "SQLITE_OFFSET( ", "SQLITE_SOURCE_ID() ", - "SQLITE_VERSION() ", "SUBSTR( ", "SUBSTRING( ", "TOTAL_CHANGES() ", "TRIM( ", "TYPEOF( ", "UNICODE( ", "UNLIKELY( ", - "UPPER( ", "ZEROBLOB( ", "DATE( ", "TIME( ", "DATETIME( ", "JULIANDAY( ", "STRFTIME( ", "DAYS ", "HOURS ", "MINUTES ", - "NOW", "SECONDS ", "MONTHS ", "YEARS ", "START OF MONTH ", "START OF YEAR ", "START OF DAY ", "WEEKDAY ", "UNIXEPOCH ", - "LOCALTIME ", "UTC ", "AVG( ", "COUNT( * ) ", "COUNT( ", "GROUP_CONCAT( ", "SUM( ", "TOTAL( ", "ACOS( ", "ACOSH( ", - "ASIN( ", "ASINH( ", "ATAN( ", "ATAN2( ", "ATANH( ", "CEIL( ", "CEILING( ", "COS( ", "COSH( ", "DEGREES( ", "EXP( ", - "FLOOR( ", "LN( ", "LOG( ", "LOG10( ", "LOG2( ", "MOD( ", "PI() ", "POW( ", "POWER( ", "RADIANS( ", "SIN( ", "SINH( ", - "SQRT( ", "TAN( ", "TANH( ", "TRUNC( ", "JSON( ", "JSON_ARRAY( ", "JSON_ARRAY_LENGTH( ", "JSON_EXTRACT( ", - "JSON_INSERT( ", "JSON_OBJECT( ", "JSON_PATCH( ", "JSON_REMOVE( ", "JSON_REPLACE( ", "JSON_SET( ", "JSON_TYPE( ", - "JSON_VALID( ", "JSON_QUOTE( ", "JSON_GROUP_ARRAY( ", "JSON_GROUP_OBJECT( ", "JSON_EACH( ", "JSON_TREE( ", - "LIST TABLES", "TABLES", "LIST DATABASES", "DATABASES", "LIST COMMANDS", "COMMANDS", "LIST INFO", "INFO", - "AUTH USER ", "USER", "PASS", "CLOSE CONNECTION ", "CONNECTION", "CREATE DATABASE ", "DATABASE", - "DISABLE PLUGIN ", "PLUGIN", "REMOVE DATABASE ", "REMOVE KEY ", "ENABLE PLUGIN ", - "GET DATABASE", "GET DATABASE ID", "ID", "GET KEY ", "LIST CONNECTIONS", "CONNECTIONS", "LIST DATABASE CONNECTIONS ", - "LIST DATABASE CONNECTIONS ID ", "LIST NODES", "LIST PLUGINS", "LIST CLIENT KEYS", - "LIST DATABASE KEYS", "LISTEN ", "NOTIFY ", "PING", "REMOVE NODE ", "SET KEY ", "UNLISTEN ", "UNUSE DATABASE", "USE DATABASE", - "on", "off", "enable", "disable", "true", "false", -} -var dynamic_tokens = []string{} - -func replaceControlChars(in string) string { - for from, to := range map[string]string{"\\0": string(0), "\\a": "\a", "\\b": "\b", "\\t": "\t", "\\n": "\n", "\\v": "\v", "\\f": "\f", "\\r": "\r"} { - in = strings.ReplaceAll(in, from, to) - } - return in -} -func getFirstNoneEmptyString(args []string) string { - for _, v := range args { - v = strings.TrimSpace(v) - if v != "" { - return v - } - } - return "" -} -func parseParameters() (Parameter, error) { - parameter := Parameter{} - var outputformat int - - // Parse Command Line Parameter (Attention: "::" -> ":") - if p, err := docopt.ParseArgs(strings.ReplaceAll(usage, "::", ": "), nil, fmt.Sprintf("%s %s, %s", app_name, version, copyright)); err == nil { - - // Preprocessing... - if format, _ := p.String("--format"); format == "" { // If --format was not specified, use default values... - if list, _ := p.Bool("--list"); list { - p["--format"] = "LIST" - } // use --format=LIST when in list mode - if output, _ := p.String("--output"); output != "" { - p["--format"] = "QUOTE" - } // use --format=QUOTE when in batch mode - } - if format, _ := p.String("--format"); format == "" { - p["--format"] = "BOX" - } // use --format=BOX when --format is stil not specified - - format, err := p.String("--format") - if err != nil { - return Parameter{}, err - } - - outputformat, err = sqlitecloud.GetOutputFormatFromString(format) - if err != nil { - return Parameter{}, err - } - - p["--outputformat"] = outputformat - - // If the connection string is set, parse and apply the connection string... - if url, isSet := p["URL"]; isSet && url != "" { - if conf, err := sqlitecloud.ParseConnectionString(reflect.ValueOf(url).String()); err == nil { - p["--host"] = getFirstNoneEmptyString([]string{dropError(p.String("--host")), conf.Host}) - p["--user"] = getFirstNoneEmptyString([]string{dropError(p.String("--user")), conf.Username}) - p["--password"] = getFirstNoneEmptyString([]string{dropError(p.String("--password")), conf.Password}) - p["--dbname"] = getFirstNoneEmptyString([]string{dropError(p.String("--dbname")), conf.Database}) - p["--host"] = getFirstNoneEmptyString([]string{dropError(p.String("--host")), conf.Host}) - p["--compress"] = getFirstNoneEmptyString([]string{dropError(p.String("--compress")), conf.CompressMode}) - if conf.Port > 0 { - p["--port"] = getFirstNoneEmptyString([]string{dropError(p.String("--port")), fmt.Sprintf("%d", conf.Port)}) - } - if conf.Timeout > 0 { - p["--timeout"] = getFirstNoneEmptyString([]string{dropError(p.String("--timeout")), fmt.Sprintf("%d", conf.Timeout)}) - } - p["--tls"] = getFirstNoneEmptyString([]string{dropError(p.String("--tls")), conf.Pem}) - p["--apikey"] = getFirstNoneEmptyString([]string{dropError(p.String("--apikey")), conf.ApiKey}) - if conf.NoBlob { - p["--noblob"] = getFirstNoneEmptyString([]string{dropError(p.String("--noblob")), strconv.FormatBool(conf.NoBlob)}) - } - if conf.MaxData > 0 { - p["--maxdata"] = getFirstNoneEmptyString([]string{dropError(p.String("--maxdata")), fmt.Sprintf("%d", conf.MaxData)}) - } - if conf.MaxRows > 0 { - p["--maxrows"] = getFirstNoneEmptyString([]string{dropError(p.String("--maxrows")), fmt.Sprintf("%d", conf.MaxRows)}) - } - if conf.MaxRowset > 0 { - p["--maxrowset"] = getFirstNoneEmptyString([]string{dropError(p.String("--maxrowset")), fmt.Sprintf("%d", conf.MaxRowset)}) - } - } - } else { - return Parameter{}, err - } - - // Set default Values - p["--host"] = getFirstNoneEmptyString([]string{dropError(p.String("--host")), "localhost"}) - p["--port"] = getFirstNoneEmptyString([]string{dropError(p.String("--port")), "8860"}) - p["--timeout"] = getFirstNoneEmptyString([]string{dropError(p.String("--timeout")), "10"}) - p["--compress"] = getFirstNoneEmptyString([]string{dropError(p.String("--compress")), "NO"}) - p["--tls"] = getFirstNoneEmptyString([]string{dropError(p.String("--tls")), "YES"}) - p["--separator"] = getFirstNoneEmptyString([]string{dropError(p.String("--separator")), dropError(sqlitecloud.GetDefaultSeparatorForOutputFormat(outputformat)), "|"}) - - // Fix invalid(=unset) parameters, quotation & control-chars - for k, v := range p { - switch reflect.ValueOf(v).Kind() { - case reflect.Invalid: - p[k] = "" - case reflect.String: - p[k] = replaceControlChars(strings.Trim(reflect.ValueOf(v).String(), "'\"")) - default: - } - } - - // for k, v := range p { fmt.Printf( "%s='%v'\r\n", k, v ) } - - // Copy map data into Object - if err := p.Bind(¶meter); err != nil { - return Parameter{}, err - } - - // Postprocessing... - if parameter.OutFile != "" { - parameter.Echo = false - parameter.Quiet = true - } - if q, _ := p.Bool("--echo"); q { - parameter.Echo = true - } - if parameter.Echo { - parameter.Quiet = false - } - if q, _ := p.Bool("--quiet"); q { - parameter.Quiet = true - parameter.Echo = false - } - - } else { - return Parameter{}, err - } - return parameter, nil -} - -func autocomplete(line string, pos int) (head string, suggestions []string, tail string) { - start := line[0:pos] - tail = strings.TrimPrefix(line[pos:], " ") - split := strings.LastIndex(start, " ") - head = "" - line = start - if split > 0 { - head = start[0 : split+1] - line = start[split+1:] - } - // fmt.Printf( "start=>%s<, tail=>%s<, head=>%s<, line=>%s<\r\n", start, tail, head, line ) - - for _, token := range dynamic_tokens { - if strings.HasPrefix(strings.ToLower(token), strings.ToLower(line)) { - suggestions = append(suggestions, token) - } - } - for _, token := range tokens { - if strings.HasPrefix(strings.ToLower(token), strings.ToLower(line)) { - suggestions = append(suggestions, token) - } - } - return -} - -func main() { - width := -1 - out := bufio.NewWriter(os.Stdout) - - if parameter, err := parseParameters(); err != nil { - fatal(out, fmt.Sprintf("ERROR: Could not parse arguments (%s).", err.Error()), ¶meter) - } else { - if parameter.OutFile != "" { - _ = os.Remove(parameter.OutFile) - if file, err := os.OpenFile(parameter.OutFile, os.O_CREATE|os.O_RDWR, 0664); err != nil { - fatal(out, fmt.Sprintf("ERROR: Could not create '%s' for writing", parameter.OutFile), ¶meter) - } else { - out = bufio.NewWriter(file) - width = 0 - defer func() { - if err := file.Close(); err != nil { - fatal(out, fmt.Sprintf("ERROR: Could not close file '%s': %s", parameter.OutFile, err), ¶meter) - } - }() - } - } - - // print( out, fmt.Sprintf( "%s %s, %s", long_name, version, copyright ), ¶meter ) - - config := sqlitecloud.SQCloudConfig{ - Host: parameter.Host, - Port: parameter.Port, - Username: parameter.User, - Password: parameter.Password, - Database: parameter.Database, - Timeout: time.Duration(parameter.Timeout) * time.Second, - CompressMode: parameter.Compress, - ApiKey: parameter.ApiKey, - NoBlob: parameter.NoBlob, - MaxData: parameter.MaxData, - MaxRows: parameter.MaxRows, - MaxRowset: parameter.MaxRowset, - } - - config.Secure, config.Pem = sqlitecloud.ParseTlsString(parameter.Tls) - var db *sqlitecloud.SQCloud = sqlitecloud.New(config) - - if err := db.Connect(); err != nil { - fatal(out, fmt.Sprintf("ERROR: %s.", err.Error()), ¶meter) - } else { - defer db.Close() - - db.Callback = func(conn *sqlitecloud.SQCloud, json string) { - print(out, json, ¶meter) - out.Flush() - } - - //print( out, fmt.Sprintf( "Connected to %s.", parameter.Host ), ¶meter ) - //print( out, strings.Split( help, "\n" )[ 0 ], ¶meter ) - - print(out, strings.ReplaceAll(banner, "", parameter.Host), ¶meter) - print(out, "", ¶meter) - - if parameter.List { - Execute(db, out, "LIST DATABASES", width, ¶meter) - os.Exit(0) - } - - // Execute single Command - if parameter.Command != "" { - Execute(db, out, parameter.Command, width, ¶meter) - } - - // Batch Mode starts here /////////////////////////////////////////////////////////////// - - // Execute Files - if len(parameter.Files) > 0 { - ExecuteFiles(db, out, parameter.Files, width, ¶meter) - } - // Execute Stdin - if parameter.UseStdIn { - if err := ExecuteBuffer(db, out, os.Stdin, width, ¶meter); err != nil { - bail(out, fmt.Sprintf("Could not execute (%s)", err.Error()), ¶meter) - } - } - // End Batch Mode - if parameter.OutFile != "" { - os.Exit(0) - } - - // Interactive Mode starts here ///////////////////////////////////////////////////////// - - editor := liner.NewLiner() - defer editor.Close() - - editor.SetCtrlCAborts(true) - editor.SetMultiLineMode(true) - editor.SetWordCompleter(autocomplete) - - if strings.HasPrefix(history_file, "~/") { // fix Home directory for POSIX and Windows - if dir, err := os.UserHomeDir(); err == nil { - history_file = fmt.Sprintf("%s/%s", dir, strings.TrimPrefix(history_file, "~/")) - } - } - if f, err := os.Open(history_file); err == nil { - editor.ReadHistory(f) - f.Close() - } - - prompt := "sqlc > " - prompt = "\\H:\\p/\\d\\u > " - - Loop: - for { - out.Flush() - db.Database, _ = db.GetDatabase() - - renderdPrompt := prompt - renderdPrompt = strings.ReplaceAll(renderdPrompt, "\\H", parameter.Host) - renderdPrompt = strings.ReplaceAll(renderdPrompt, "\\p", fmt.Sprintf("%d", parameter.Port)) - renderdPrompt = strings.ReplaceAll(renderdPrompt, "\\u", parameter.User) - renderdPrompt = strings.ReplaceAll(renderdPrompt, "\\d", db.Database) - - renderdPrompt = strings.ReplaceAll(renderdPrompt, "\\T", time.Now().Format("2006-01-02")) - renderdPrompt = strings.ReplaceAll(renderdPrompt, "\\t", time.Now().Format("15:04:05")) - renderdPrompt = strings.ReplaceAll(renderdPrompt, "\\w", fmt.Sprintf("/%s", dropError(os.Getwd()))) - - go func() { dynamic_tokens = db.GetAutocompleteTokens() }() // Update the dynamic tokens in the background... - command, err := editor.Prompt(renderdPrompt) - - switch err { - case io.EOF: - break - case nil: - switch tokens := strings.Split(strings.ToLower(strings.TrimSpace(command)), " "); tokens[0] { - case "": - continue Loop - case ".exit", "exit": - break Loop - case ".help": - print(out, help, ¶meter) - case ".bail": - parameter.Bail = getNextTokenValueAsBool(out, parameter.Bail, tokens, ¶meter) - case ".echo": - parameter.Echo = getNextTokenValueAsBool(out, parameter.Echo, tokens, ¶meter) - case ".quiet": - parameter.Quiet = getNextTokenValueAsBool(out, parameter.Quiet, tokens, ¶meter) - case ".noheader": - parameter.NoHeader = getNextTokenValueAsBool(out, parameter.NoHeader, tokens, ¶meter) - case ".width": - width = getNextTokenValueAsInteger(out, width, -1, tokens, ¶meter) - case ".nullvalue": - parameter.NullText = getNextTokenValueAsString(out, parameter.NullText, "NULL", "", tokens, ¶meter) - case ".newline": - parameter.NewLine = getNextTokenValueAsString(out, parameter.NewLine, "\r\n", "", tokens, ¶meter) - case ".prompt": - prompt = getNextTokenValueAsString(out, prompt, "sqlc >", "", tokens, ¶meter) - case ".separator": - parameter.Separator = getNextTokenValueAsString(out, parameter.Separator, "", "", tokens, ¶meter) - case ".timeout": - parameter.Timeout = getNextTokenValueAsInteger(out, parameter.Timeout, 10, tokens, ¶meter) - case ".compress": - parameter.Compress = getNextTokenValueAsString(out, parameter.Compress, "NO", "|no|lz4|", tokens, ¶meter) - db.Compress(parameter.Compress) - - case ".format": - newFormat := getNextTokenValueAsString(out, parameter.Format, "BOX", "|list|csv|quote|tabs|line|json|html|xml|markdown|table|box|", tokens, ¶meter) - if newFormat != parameter.Format { - parameter.Format = newFormat - parameter.Separator = "" - parameter.OutPutFormat, _ = sqlitecloud.GetOutputFormatFromString(newFormat) - } - - default: - Execute(db, out, command, width, ¶meter) - switch tokens[0] { - case ".quit", "quit": - break Loop - default: - editor.AppendHistory(command) - } - } - default: - bail(out, err.Error(), ¶meter) - } - } - - if f, err := os.Create(history_file); err == nil { - _, _ = editor.WriteHistory(f) - _ = f.Close() - } - } - } -} - -func Execute(db *sqlitecloud.SQCloud, out *bufio.Writer, cmd string, width int, Settings *Parameter) { - Seperator := Settings.Separator - if cmd == "" { - return - } - if Settings.OutPutFormat == sqlitecloud.OUTFORMAT_XML { - Seperator = cmd - } - if width < 0 { - if w, _, err := term.GetSize(0); err == nil { - width = w - } - } - if Settings.Echo { - print(out, cmd, Settings) - } - - start := time.Now() - if res, err := db.Select(cmd); res != nil { - defer res.Free() - - if err == nil { - if !res.IsRowSet() || (res.GetNumberOfRows() > 0 && res.GetNumberOfColumns() > 0) { - res.DumpToWriter(out, Settings.OutPutFormat, Settings.NoHeader, Seperator, Settings.NullText, Settings.NewLine, uint(width), Settings.Quiet) - } - if res.IsRowSet() { - print(out, fmt.Sprintf("Rows: %d - Cols: %d: %s Time: %s", res.GetNumberOfRows(), res.GetNumberOfColumns(), renderByteCount(int64(res.GetUncompressedChuckSizeSum())), time.Since(start)), Settings) - } - } - } else { - bail(out, err.Error(), Settings) - // bail(out, err.Error()+fmt.Sprintf(" (%d:%d:%d)", db.GetErrorCode(), db.GetExtErrorCode(), db.GetErrorOffset()), Settings) - } - print(out, "", Settings) // Empty line -} - -func ExecuteBuffer(db *sqlitecloud.SQCloud, out *bufio.Writer, in *os.File, width int, Settings *Parameter) error { - if scanner := bufio.NewScanner(in); scanner != nil { - for scanner.Scan() { - line := scanner.Text() - if strings.ToUpper(line) == ".GOTO_PROMPT" { - return nil // break out of sql script - } - if strings.TrimSpace(line) != "" { - Execute(db, out, line, width, Settings) - } - } - return scanner.Err() // nil or some error - } - return errors.New("Could not instanciate the line scanner") -} -func ExecuteFile(db *sqlitecloud.SQCloud, out *bufio.Writer, FilePath string, width int, Settings *Parameter) error { - file, err := os.Open(FilePath) - if err == nil { - defer func() { - _ = file.Close() - }() - if err := ExecuteBuffer(db, out, file, width, Settings); err != nil { - return err - } - } - return err -} -func ExecuteFiles(db *sqlitecloud.SQCloud, out *bufio.Writer, FilePathes []string, width int, Settings *Parameter) error { - for _, file := range FilePathes { - err := ExecuteFile(db, out, file, width, Settings) - if err != nil { - return err - } - } - return nil -} - -func print(out *bufio.Writer, Message string, Settings *Parameter) { - if !Settings.Quiet { - _, _ = io.WriteString(out, Message) - _, _ = io.WriteString(out, Settings.NewLine) - } -} -func bail(out *bufio.Writer, Message string, Settings *Parameter) { - print(out, Message, Settings) - if Settings.Bail { - os.Exit(1) - } -} -func fatal(out *bufio.Writer, Message string, Settings *Parameter) { - _, _ = io.WriteString(out, Message) - _, _ = io.WriteString(out, Settings.NewLine) - _ = out.Flush() - os.Exit(1) -} - -func renderByteCount(count int64) string { - const unit = 1000 - if count < unit { - return fmt.Sprintf("%d Bytes", count) - } - div, exp := int64(unit), 0 - for n := count / unit; n >= unit; n /= unit { - div *= unit - exp++ - } - return fmt.Sprintf("%.1f %cBytes", float64(count)/float64(div), "kMGTPE"[exp]) -} - -func dropError(val string, err error) string { return val } -func getNextTokenValueAsBool(out *bufio.Writer, oldValue bool, tokens []string, Settings *Parameter) bool { - switch len(tokens) { - case 1: - return !oldValue - case 2: - switch tokens[1] { - case "on", "true", "1", "enable": - return true - case "off", "false", "0", "disable": - return false - default: - bail(out, fmt.Sprintf("SYNTHAX ERROR: Invalid parameter \"%s\".", tokens[1]), Settings) - } - default: - bail(out, "SYNTHAX ERROR: Wrong number of parameters.", Settings) - } - return oldValue -} -func getNextTokenValueAsInteger(out *bufio.Writer, oldValue int, defaultValue int, tokens []string, Settings *Parameter) int { - switch len(tokens) { - case 1: - return defaultValue - case 2: - if i, err := strconv.Atoi(tokens[1]); err == nil { - return i - } - bail(out, fmt.Sprintf("SYNTHAX ERROR: \"%s\" is not a valid number.", tokens[1]), Settings) - default: - bail(out, "SYNTHAX ERROR: Wrong number of parameters.", Settings) - } - return oldValue -} -func getNextTokenValueAsString(out *bufio.Writer, oldValue string, defaultValue string, allowedValues string, tokens []string, Settings *Parameter) string { - switch len(tokens) { - case 1: - return defaultValue - case 2: - if allowedValues == "" { - return replaceControlChars(tokens[1]) - } - if strings.Contains(allowedValues, fmt.Sprintf("|%s|", tokens[1])) { - return tokens[1] - } - bail(out, fmt.Sprintf("SYNTHAX ERROR: Invalid parameter \"%s\".", tokens[1]), Settings) - default: - bail(out, "SYNTHAX ERROR: Wrong number of parameters.", Settings) - } - return oldValue -} diff --git a/GO/sdk/ERRORs.md b/GO/sdk/ERRORs.md deleted file mode 100644 index 0ec516ac..00000000 --- a/GO/sdk/ERRORs.md +++ /dev/null @@ -1,40 +0,0 @@ -# Error Codes -``` -12334 -│││││ -││││└─ Error Reason -│││└── Data Type -││└─── Class -│└──── Module -``` - -## Error Reason - -| Code | Alternative | Explanation | -|------|-------------|-------------| -| 0 | - | Value is NULL/NIL/ZERO | -| 1 | - | Value is to small | -| 2 | - | Value is to large | -| 3 | 1 + 2 | Value is out of bounds | - - -## Data Type - -| Code | Alternative | Explanation | -|------|-------------|-------------| -| 10 | - | Bool Value | -| 20 | - | Integer String | -| 40 | - | Float String | -| 80 | - | String Value | -| 140 | 20 + 40 + 80 | Date Value | -| 160 | - | Object - -## Class - -| Code | Alternative | Explanation | -|------|-------------|-------------| - -## Module - -| Code | Alternative | Explanation | -|------|-------------|-------------| \ No newline at end of file diff --git a/GO/sdk/PUBSUB.md b/GO/sdk/PUBSUB.md deleted file mode 100644 index 0f7e510c..00000000 --- a/GO/sdk/PUBSUB.md +++ /dev/null @@ -1,180 +0,0 @@ -#### Server side Pub/Sub implementation details - -SQLiteCloud listens to a default port (default 8860) where clients can connect (using SSL and prior to authentication). The default port is used to read commands from clients, process that commands and sends a reply. Socket flows is: - -1. READ FROM SOCKET -2. PROCESS REQUEST -3. SEND A REPLY - -When a client send the first **LISTEN channel** command a special reply is sent back to it. This reply is a special command that must be re-executed as is on server side. Command is: - -**PAUTH** **client_uuid** **client_secret** - -- When executed on **client-side**, the client opens a new connection to the server and a new thread is created listening for read events on this new socket. -- When executed on **server-side**, client is recognized (using uuid) and authenticated (using secret) and its pubsub socket is set to the current socket used by this new connection. This socket will be used exclusively to deliver notifications. Socket flow is different from the default one because it involves WRITE operations only. - -The following commands are related to PUB/SUB: - -1. **LISTEN channel**: registers the current client as a listener on the notification channel named `channel`. If the current session is already registered as a listener for this notification channel, nothing is done. If channel has the same name of the current database (if any) table then all WRITE operations will be notified. If channel is `*` the all WRITE operations of all the tables of the current database (if any) will be notified. LISTEN takes effect at transaction commit. If channel is not `*` and it is not the name of table in the current database (if any), then it represents a named channel that can be notified only by NOTIFY channel commands. -2. **UNLISTEN channel**: remove an existing registration for `NOTIFY` events. `UNLISTEN` cancels any existing registration of the current SQLiteCloud session as a listener on the notification channel named `channel`. The special wildcard `*` cancels all listener registrations for the current session. -3. **NOTIFY channel [, payload]**: The `NOTIFY` command sends a notification event together with an optional "payload" string to each client application that has previously executed `LISTEN channel` for the specified channel name in the current database. The payload (if any) is broadcast as is to all other connections without any modification on server side. - - - -**PUB/SUB FORMAT** - -JSON is used to deliver payload to all listening clients. Format changes depending on the operation type. In case of database tables, notifications occur on COMMIT so the same JSON can collect more changes related to that table. Server guarantees **one JSON per channel**. - - - -**1. NOTIFY payload** - -Format: -``` -{ - sender: "UUID", - channel: "name", - type: "MESSAGE", - payload: "Message content here" // payload is optional -} -``` - - -**2. TABLE modification payload** -``` -{ - sender: "UUID", - channel: "tablename", - type: "TABLE", - pk: ["id", col1"] // array of primary key name(s) - payload: [ // array of operations that affect table name - { - type: "INSERT", - id: 12, - col1: "value1", - col2: 3.14 - }, - { - type: "DELETE", - pv: [13] // primary key value (s) in the same order as the pk array - }, - { - type: "UPDATE", - id: 15, // new value - col1: "newvalue", - col2: 0.0 - // if primary key is updated during this update then add it to: - // UPDATE TABLE SET col1='newvalue', col2=0.0, id = 15 WHERE id=14 - pv: [14] // primary key value (s) set prior to this UPDATE operation - ] - } - ] -} -``` - -**Details:** - -* `sender`: is the UUID of the client who sent the NOTIFY event or who initiated the WRITE operation that triggers the notification. It is common for a client that executes `NOTIFY` to be listening on the same notification channel itself. In that case it will get back a notification event, just like all the other listening sessions. Depending on the application logic, this could result in useless work, for example, reading a database table to find the same updates that that session just wrote out. It is possible to avoid such extra work by noticing whether the notifying `UUID` (supplied in the notification event message) is the same as one's `UUID` (available from SDK). When they are the same, the notification event is one's own work bouncing back, and can be ignored. If `UUID` is 0 it means that server sent that payload. -* `channel`: this field represents the channel/table affected. -* `type`: determine the type of operation, it can be: MESSAGE, TABLE, INSERT, UPDATE, or DELETE (more to come). -* `pk/pv`: these fields represent the primary key name(s) and value(s) affected by this table operation. -* `payload`: todo - -**Examples:** -``` -USE DATABASE test.sqlite -LISTEN foo -``` -``` -foo is a table created as: -CREATE TABLE "foo" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "col1" TEXT, "col2" TEXT) -``` - -``` -DELETE FROM foo WHERE id=14; - -{ - "sender": "b7a92805-ef82-4ad1-8c2f-92da6df6b1d5", - "channel": "foo", - "type": "TABLE", - "pk": ["id"], - "payload": [{ - "type": "DELETE", - "pv": [14] - }] -} -``` - -``` -INSERT INTO foo(col1, col2) VALUES ('test100', 'test101'); - -{ - "sender": "b7a92805-ef82-4ad1-8c2f-92da6df6b1d5", - "channel": "foo", - "type": "TABLE", - "pk": ["id"], - "payload": [{ - "type": "INSERT", - "id": 15, - "col1": "test100", - "col2": "test101" - }] -} -``` - -``` -UPDATE foo SET id=14,col1='test200' WHERE id=15; - -{ - "sender": "b7a92805-ef82-4ad1-8c2f-92da6df6b1d5", - "channel": "foo", - "type": "TABLE", - "pk": ["id"], - "payload": [{ - "type": "DELETE", - "pv": [15] - }, { - "type": "INSERT", - "id": 14, - "col1": "test200", - "col2": "test101" - }] -} -``` - - -#### Useful links: - -https://www.postgresql.org/docs/current/sql-notify.html - -https://www.postgresql.org/docs/current/sql-listen.html - -https://tapoueh.org/blog/2018/07/postgresql-listen-notify/ - -https://www.postgresql.org/docs/9.1/libpq-example.html - -https://www.postgresql.org/docs/9.5/libpq-example.html#LIBPQ-EXAMPLE-2 - -https://redis.io/topics/pubsub - -https://thoughtbot.com/blog/redis-pub-sub-how-does-it-work - -https://github.com/redis/hiredis/blob/master/test.c - -https://www.toptal.com/go/going-real-time-with-redis-pubsub - - - -#### Implementation: - -Use pusher instead of re-implementing it? https://pusher.com - -https://making.pusher.com/redis-pubsub-under-the-hood/ - -https://making.pusher.com/how-pusher-channels-has-delivered-10000000000000-messages/ - -https://github.com/nanopack/mist - -https://github.com/lileio/pubsub - -https://itnext.io/redis-as-a-pub-sub-engine-in-go-10eb5e6699cc diff --git a/GO/sdk/aux.go b/GO/sdk/aux.go deleted file mode 100644 index 733e299c..00000000 --- a/GO/sdk/aux.go +++ /dev/null @@ -1,316 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud GO SDK -// /// /// /// Version : 1.1.0 -// // /// /// /// Date : 2021/10/01 -// /// /// /// /// Author : Andreas Pfeil -// /// /// /// /// -// /// ////////// /// /// Description : Auxiliary GO methods -// //// /// /// related to the SQCloud -// //// ////////// /// class. -// //// //// ( Convenience API's ) -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -package sqlitecloud - -import ( - "errors" - "fmt" -) - -// BeginTransaction starts a transaction. -// To finish a transaction, see: SQCloud.EndTransaction(), to undo a transaction, see: SQCloud.RollBackTransaction() -func (this *SQCloud) BeginTransaction() error { - return this.Execute("BEGIN TRANSACTION") -} - -// EndTransaction finishes a transaction and writes the changes to the disc. -// To start a transaction, see: SQCloud.BeginTransaction(), to undo a transaction, see: SQCloud.RollBackTransaction(). -func (this *SQCloud) EndTransaction() error { - return this.Execute("END TRANSACTION") -} - -// RollBackTransaction aborts a transaction without the writing the changes to the disc. -// To start a transaction, see: SQCloud.BeginTransaction(), to finish a transaction, see: SQCloud.EndTransaction(). -func (this *SQCloud) RollBackTransaction() error { - return this.Execute("ROLLBACK TRANSACTION") -} - -func (this *SQCloud) GetAutocompleteTokens() (tokens []string) { - if tables, err := this.ListTables(); err == nil { - for _, table := range tables { - if table != "sqlite_sequence" { - tokens = append(tokens, table) - for _, column := range this.ListColumns(table) { - tokens = append(tokens, fmt.Sprintf("%s", column)) - tokens = append(tokens, fmt.Sprintf("%s.%s", table, column)) - } - } - } - } - return -} - -func (this *SQCloud) ListColumns(TableName string) (columns []string) { - res, err := this.Select(fmt.Sprintf("pragma table_info( %s )", SQCloudEnquoteString(TableName))) - if res != nil { - defer res.Free() - - if err == nil { - for row, err := res.GetFirstRow(); row != nil; row = row.Next() { - switch { - case err != nil: - break - case row == nil: - break - default: - switch val, err := row.GetString(1); { - case err != nil: - break - default: - columns = append(columns, val) - } - } - } - } - } - return -} - -// SelectSingleString executes the query in SQL and returns the result as string. -// The given query must return one single value. If no value or more than one values are selected by the query, nil is -// returned and error describes the problem that occurred. -// See: SQCloud.SelectSingleInt64(), SQCloud.SelectStringList(), SQCloud.SelectKeyValues() -func (this *SQCloud) SelectSingleString(SQL string) (string, error) { - if result, err := this.Select(SQL); result == nil { - return "", errors.New("ERROR: Query returned no result (-1)") - - } else { - defer result.Free() - - switch { - case err != nil: - return "", err - case result.IsError(): - return "", errors.New(result.GetErrorAsString()) - case result.IsString(): - return result.GetString() - case !result.IsRowSet(): - return "", errors.New("ERROR: Query returned an invalid result") - case result.GetNumberOfRows() != 1: - return "", errors.New("ERROR: Query returned not exactly one row") - case result.GetNumberOfColumns() != 1: - return "", errors.New("ERROR: Query returned not exactly one column") - case result.GetValueType_(0, 0) != '+': - return "", errors.New("ERROR: Query returned not a string") - default: - return result.GetStringValue(0, 0) - } - } -} - -// SelectSingleInt64 executes the query in SQL and returns the result as int64. -// The given query must return one single numerical value. If no value, more than one values or a non-numerical value is selected by the query, 0 is -// returned and error describes the problem that occurred. -// See: SQCloud.SelectSingleString(), SQCloud.SelectStringList(), SQCloud.SelectKeyValues() -func (this *SQCloud) SelectSingleInt64(SQL string) (int64, error) { - if result, err := this.Select(SQL); result == nil { - return 0, errors.New("ERROR: Query returned no result (-1)") - - } else { - defer result.Free() - - switch { - case err != nil: - return 0, err - case result.IsError(): - return 0, errors.New(result.GetErrorAsString()) - case result.IsInteger(): - return result.GetInt64() - case !result.IsRowSet(): - return 0, errors.New("ERROR: Query returned an invalid result") - case result.GetNumberOfRows() != 1: - return 0, errors.New("ERROR: Query returned not exactly one row") - case result.GetNumberOfColumns() != 1: - return 0, errors.New("ERROR: Query returned not exactly one column") - case result.GetValueType_(0, 0) != ':': - return 0, errors.New("ERROR: Query returned not an integer") - default: - return result.GetInt64Value(0, 0) - } - } -} - -// SelectStringList executes the query in SQL and returns the result as an array of strings. -// The given query must result in 0 or more rows and one column. -// If no row was selected, an empty string array is returned. -// If more than one column was selected, an empty string array and an error describing the problem is returned. -// In all other cases, an array of strings is returned. -// See: SQCloud.SelectSingleString(), SQCloud.SelectSingleInt64(), SQCloud.SelectKeyValues() -func (this *SQCloud) SelectStringList(SQL string) ([]string, error) { - return this.SelectStringListWithCol(SQL, 0) -} - -func (this *SQCloud) SelectStringListWithCol(SQL string, col uint64) ([]string, error) { - if result, err := this.Select(SQL); result == nil { - return []string{}, errors.New("ERROR: Query returned no result (-1)") - - } else { - defer result.Free() - - switch { - case err != nil: - return []string{}, err - case result.IsError(): - return []string{}, errors.New(result.GetErrorAsString()) - case result.IsString(): - return []string{result.GetString_()}, nil - case result.IsNULL(): - return []string{}, nil - case !result.IsRowSet(): - return []string{}, errors.New("ERROR: Query returned an invalid result") - // case result.GetNumberOfColumns() != 1: - // return []string{}, errors.New("ERROR: Query returned not exactly one column") - default: - stringList := []string{} - for _, row := range result.Rows() { - switch val, err := row.GetValue(col); { - case err != nil: - return []string{}, err - case val == nil: - return []string{}, errors.New("ERROR: Value in query result is invalid") - case !val.IsString(): - return []string{}, errors.New("ERROR: Value in query result is not a string") - default: - stringList = append(stringList, val.GetString()) - } - } - return stringList, nil - } - } -} - -func resultToMap(result *Result, err error) (map[string]interface{}, error) { - if result == nil { - return map[string]interface{}{}, errors.New("ERROR: Query returned no result (-1)") - - } else { - defer result.Free() - - switch { - case err != nil: - return map[string]interface{}{}, err - case result.IsError(): - return map[string]interface{}{}, errors.New(result.GetErrorAsString()) - case result.IsNULL(): - return map[string]interface{}{}, nil - case !result.IsRowSet(): - return map[string]interface{}{}, errors.New("ERROR: Query returned an invalid result") - case result.GetNumberOfColumns() != 2: - return map[string]interface{}{}, errors.New("ERROR: Query returned not exactly two columns") - default: - keyValueList := map[string]interface{}{} - for _, row := range result.Rows() { - key, kerr := row.GetValue(0) - val, verr := row.GetValue(1) - switch { - case kerr != nil: - return map[string]interface{}{}, kerr - case verr != nil: - return map[string]interface{}{}, verr - case key == nil: - return map[string]interface{}{}, errors.New("ERROR: Key in query result is invalid") - case val == nil: - case !key.IsString(): - return map[string]interface{}{}, errors.New("ERROR: Key in query result is not a string") - case val.IsString(): - keyValueList[key.GetString()] = val.GetString() - case val.IsBLOB(): - keyValueList[key.GetString()] = val.GetBuffer() - case val.IsFloat(): - keyValueList[key.GetString()], _ = val.GetFloat64() - case val.IsInteger(): - keyValueList[key.GetString()], _ = val.GetInt64() - case val.IsNULL(): - keyValueList[key.GetString()] = nil - // case val.IsJSON(): - default: - return map[string]interface{}{}, errors.New(fmt.Sprintf("ERROR: Value type %v not supported", val.GetType())) - } - } - return keyValueList, nil - } - } -} - -func resultToKeyValues(result *Result, err error) (map[string]string, error) { - if result == nil { - return map[string]string{}, errors.New("ERROR: Query returned no result (-1)") - - } else { - defer result.Free() - - switch { - case err != nil: - return map[string]string{}, err - case result.IsError(): - return map[string]string{}, errors.New(result.GetErrorAsString()) - case result.IsNULL(): - return map[string]string{}, nil - case !result.IsRowSet(): - return map[string]string{}, errors.New("ERROR: Query returned an invalid result") - case result.GetNumberOfColumns() != 2: - return map[string]string{}, errors.New("ERROR: Query returned not exactly two columns") - default: - keyValueList := map[string]string{} - for _, row := range result.Rows() { - key, kerr := row.GetValue(0) - val, verr := row.GetValue(1) - switch { - case kerr != nil: - return map[string]string{}, kerr - case verr != nil: - return map[string]string{}, verr - case key == nil: - return map[string]string{}, errors.New("ERROR: Key in query result is invalid") - case val == nil: - return map[string]string{}, errors.New("ERROR: Value in query result is invalid") - case !key.IsString(): - return map[string]string{}, errors.New("ERROR: Key in query result is not a string") - case val.IsNULL(): - keyValueList[key.GetString()] = "" - case !val.IsString(): - return map[string]string{}, errors.New("ERROR: Value in query result is not a string") - default: - keyValueList[key.GetString()] = val.GetString() - } - } - return keyValueList, nil - } - } -} - -func (this *SQCloud) SelectMap(SQL string) (map[string]interface{}, error) { - result, err := this.Select(SQL) - return resultToMap(result, err) -} - -// SelectKeyValues executes the query in SQL and returns the result as an array of SQCloudKeyValues. -// The given query must result in 0 or more rows and two columns. -// If no row was selected, an empty array is returned. -// If less or more than two columns where selected, an empty array and an error describing the problem is returned. -// In all other cases, an array of SQCloudKeyValues is returned. -// See: SQCloud.SelectSingleString(), SQCloud.SelectSingleInt64(), SQCloud.SelectStringList() -func (this *SQCloud) SelectKeyValues(SQL string) (map[string]string, error) { - result, err := this.Select(SQL) - return resultToKeyValues(result, err) -} - -func (this *SQCloud) SelectArrayKeyValues(SQL string, values []interface{}) (map[string]string, error) { - result, err := this.SelectArray(SQL, values) - return resultToKeyValues(result, err) -} diff --git a/GO/sdk/c_proxy.stubs b/GO/sdk/c_proxy.stubs deleted file mode 100644 index 6fd686ef..00000000 --- a/GO/sdk/c_proxy.stubs +++ /dev/null @@ -1,163 +0,0 @@ - -// SQCloudConnection *SQCloudConnect (const char *hostname, int port, SQCloudConfig *config); -func CConnect( Host string, Port int, Username string, Password string, Database string, Timeout int, Family int ) *C.struct_SQCloudConnection { - conf := C.struct_SQCloudConfigStruct{} - conf.username = C.CString( Username ) - conf.password = C.CString( Password ) - conf.database = C.CString( Database ) - conf.timeout = C.int( Timeout ) - conf.family = C.int( Family ) - - cHost := C.CString( Host ) - - cConnection := C.SQCloudConnect( cHost, C.int( Port ), &conf ) // nil, &conf - - C.free( unsafe.Pointer( cHost ) ) - C.free( unsafe.Pointer( conf.database ) ) - C.free( unsafe.Pointer( conf.password ) ) - C.free( unsafe.Pointer( conf.username ) ) - - return cConnection -} - -// SQCloudConnection *SQCloudConnectWithString (const char *s); -func CConnectWithString( ConnectionString string ) *SQCloud { - cConString := C.CString( ConnectionString ) - connection := SQCloud{ connection: C.SQCloudConnectWithString( cConString ) } - C.free( unsafe.Pointer( cConString ) ) - - if connection.connection == nil { - return nil - } - - return &connection -} - -// void SQCloudDisconnect (SQCloudConnection *connection); -func (this *SQCloud ) CDisconnect() { - if this.connection != nil { - C.SQCloudDisconnect( this.connection ) - this.connection = nil - } -} -// char *SQCloudUUID (SQCloudConnection *connection); -func (this *SQCloud ) CGetCloudUUID() string { - return C.GoString( C.SQCloudUUID( this.connection ) ) -} - -//bool SQCloudIsError (SQCloudConnection *connection); -func (this *SQCloud ) CIsError() bool { - return bool( C.SQCloudIsError( this.connection ) ) -} -//int SQCloudErrorCode (SQCloudConnection *connection); -func (this *SQCloud ) CGetErrorCode() int { - return int( C.SQCloudErrorCode( this.connection ) ) -} -//const char *SQCloudErrorMsg (SQCloudConnection *connection); -func (this *SQCloud ) CGetErrorMessage() string { - return C.GoString( C.SQCloudErrorMsg( this.connection ) ) -} - -// SQCloudResult *SQCloudExec (SQCloudConnection *connection, const char *command); -func (this *SQCloud ) CExec( Command string ) *SQCloudResult { - cCommand := C.CString( Command ) - defer C.free( unsafe.Pointer( cCommand ) ) - - //println( "exec ("+Command+").." ) - - result := SQCloudResult{ result: C.SQCloudExec( this.connection, cCommand ) } - // println( "done.") - if result.result == nil { - return nil - } - return &result -} -// SQCloudResult *SQCloudSetPubSubOnly (SQCloudConnection *connection); -func (this *SQCloud ) CSetPubSubOnly() *SQCloudResult { - result := SQCloudResult{ result: C.SQCloudSetPubSubOnly( this.connection ) } - - if result.result == nil { - return nil - } - - return &result -} -// SQCloudResType SQCloudResultType (SQCloudResult *result); -func (this *SQCloudResult ) CGetResultType() uint { - return uint( C.SQCloudResultType( this.result ) ) -} -// uint32_t SQCloudResultLen (SQCloudResult *result); -func (this *SQCloudResult ) CGetResultLen() uint { - return uint( C.SQCloudResultLen( this.result ) ) -} -// char *SQCloudResultBuffer (SQCloudResult *result); -func (this *SQCloudResult ) CGetResultBuffer() string { - return C.GoStringN( C.SQCloudResultBuffer( this.result ), C.int( this.CGetResultLen() ) ) -} -// void SQCloudResultFree (SQCloudResult *result); -func (this *SQCloudResult ) CFree() { - C.SQCloudResultFree( this.result ) -} -// bool SQCloudResultIsOK (SQCloudResult *result); -func (this *SQCloudResult ) CIsOK() bool { - return bool( C.SQCloudResultIsOK( this.result ) ) -} -// SQCloudValueType SQCloudRowsetValueType (SQCloudResult *result, uint32_t row, uint32_t col); -func (this *SQCloudResult ) CGetValueType( Row uint, Column uint ) int { - return int( C.SQCloudRowsetValueType( this.result, C.uint( Row ), C.uint( Column ) ) ) -} -// uint32_t SQCloudResultMaxColumnLenght (SQCloudResult *result, uint32_t col) ; -func (this *SQCloudResult ) CGetMaxColumnLenght( Column uint ) uint { - return uint( C.SQCloudRowsetRowsMaxColumnLength( this.result, C.uint( Column ) ) ) -} -// char *SQCloudRowsetColumnName (SQCloudResult *result, uint32_t col, uint32_t *len); -func (this *SQCloudResult ) CGetColumnName( Column uint ) string { - var len C.uint32_t = 0 - return C.GoStringN( C.SQCloudRowsetColumnName( this.result, C.uint( Column ), &len ), C.int( len ) ) -} -// uint32_t SQCloudRowsetRows (SQCloudResult *result); -func (this *SQCloudResult ) CGetRows() uint { - return uint( C.SQCloudRowsetRows( this.result ) ) -} -// uint32_t SQCloudRowsetCols (SQCloudResult *result); -func (this *SQCloudResult ) CGetColumns() uint { - return uint( C.SQCloudRowsetCols( this.result ) ) -} -// uint32_t SQCloudRowsetMaxLen (SQCloudResult *result); -func (this *SQCloudResult ) CGetMaxLen() uint32 { - return uint32( C.SQCloudRowsetMaxLen( this.result ) ) -} -// char *SQCloudRowsetValue (SQCloudResult *result, uint32_t row, uint32_t col, uint32_t *len); -func (this *SQCloudResult ) CGetStringValue( Row uint, Column uint ) string { - var len C.uint32_t = 0 - return C.GoStringN( C.SQCloudRowsetValue( this.result, C.uint32_t( Row ), C.uint32_t( Column ), &len ), C.int( len ) ) // Problem: NULL Pointer in return -} -// int32_t SQCloudRowsetInt32Value (SQCloudResult *result, uint32_t row, uint32_t col); -func (this *SQCloudResult ) CGetInt32Value( Row uint, Column uint ) int32 { - return int32( C.SQCloudRowsetInt32Value( this.result, C.uint( Row ), C.uint( Column ) ) ) -} -// int64_t SQCloudRowsetInt64Value (SQCloudResult *result, uint32_t row, uint32_t col); -func (this *SQCloudResult ) CGetInt64Value( Row uint, Column uint ) int64 { - return int64( C.SQCloudRowsetInt64Value( this.result, C.uint( Row ), C.uint( Column ) ) ) -} -// float SQCloudRowsetFloatValue (SQCloudResult *result, uint32_t row, uint32_t col); -func (this *SQCloudResult ) CGetFloat32Value( Row uint, Column uint ) float32 { - return float32( C.SQCloudRowsetFloatValue( this.result, C.uint( Row ), C.uint( Column ) ) ) -} -// double SQCloudRowsetDoubleValue (SQCloudResult *result, uint32_t row, uint32_t col); -func (this *SQCloudResult ) CGetFloat64Value( Row uint, Column uint ) float64 { - return float64( C.SQCloudRowsetDoubleValue( this.result, C.uint( Row ), C.uint( Column ) ) ) -} -// void SQCloudRowsetDump (SQCloudResult *result, uint32_t maxline); -func (this *SQCloudResult ) CDump( MaxLine uint ) { - C.SQCloudRowsetDump( this.result, C.uint( MaxLine ) ) -} - -// Reserverd (internal) functions - will never be exported - -// bool SQCloudForwardExec(SQCloudConnection *connection, const char *command, bool (*forward_cb) (char *buffer, size_t blen, void *xdata), void *xdata) { -// SQCloudResult *SQCloudSetUUID (SQCloudConnection *connection, const char *UUID) - -// Will be implemented in GO - -// void SQCloudSetPubSubCallback (SQCloudConnection *connection, SQCloudPubSubCB callback, void *data); \ No newline at end of file diff --git a/GO/sdk/chunk.go b/GO/sdk/chunk.go deleted file mode 100644 index 1a3e81fa..00000000 --- a/GO/sdk/chunk.go +++ /dev/null @@ -1,547 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud GO SDK -// /// /// /// Version : 1.1.1 -// // /// /// /// Date : 2021/10/13 -// /// /// /// /// Author : Andreas Pfeil -// /// /// /// /// -// /// ////////// /// /// Description : Go Methods related to the -// //// /// /// SQCloud class for managing -// //// ////////// /// the connection and executing -// //// //// queries. -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -package sqlitecloud - -import ( - "errors" - "fmt" - "io" - "net" - "strconv" - "time" - - "github.com/pierrec/lz4" -) - -type Chunk struct { - DataBufferOffset uint64 - LEN uint64 - RAW []byte -} - -func (this *Chunk) GetType() byte { return this.RAW[0] } -func (this *Chunk) IsCompressed() bool { return this.GetType() == '%' } -func (this *Chunk) GetChunkSize() uint64 { return uint64(len(this.RAW)) } -func (this *Chunk) GetData() []byte { - switch this.RAW { - case nil: - return []byte{'_', ' '} - default: - return this.RAW[this.DataBufferOffset:] - } -} - -func (this *Chunk) Uncompress() error { - // %TLEN CLEN ULEN *LEN 0:VERSION NROWS NCOLS - // %TLEN CLEN ULEN /LEN IDX:VERSION NROWS NCOLS - - if this.RAW == nil { - return errors.New("Nil pointer exception") - } - if !this.IsCompressed() { - return nil - } - - var err error - - var hStartIndex uint64 = 1 // Index of the start of the uncompressed header in chunk (*0 NROWS NCOLS ...) - var zStartIndex uint64 = 0 // Index of the start of the compressed buffer in this chunk () - - var LEN uint64 = 0 - var lLEN uint64 = 0 - - var COMPRESSED uint64 = 0 - var cLEN uint64 = 0 - - var UNCOMPRESSED uint64 = 0 - var iUNCOMPRESSED int = 0 - var uLEN uint64 = 0 - - LEN, _, lLEN, err = this.readUInt64At(hStartIndex) // "%TLEN " - hStartIndex += lLEN // hStartIndex -> "CLEN ULEN *0 NROWS NCOLS " - - COMPRESSED, _, cLEN, err = this.readUInt64At(hStartIndex) // "CLEN " - hStartIndex += cLEN // hStartIndex -> "ULEN *0 NROWS NCOLS " - - UNCOMPRESSED, _, uLEN, err = this.readUInt64At(hStartIndex) // "ULEN " - hStartIndex += uLEN // hStartIndex -> "*0 NROWS NCOLS " - - zStartIndex = LEN - COMPRESSED + lLEN + 1 // zStartIndex -> "" - hLEN := zStartIndex - hStartIndex // = len( "*0 NROWS NCOLS " ) - - newHeader := fmt.Sprintf("%c%d %s", this.RAW[hStartIndex], UNCOMPRESSED+hLEN-3, string(this.RAW[hStartIndex+3:hStartIndex+hLEN])) - - this.DataBufferOffset = uint64(len(newHeader)) // = len( "*200020 1000 2 " ) - - buf := make([]byte, this.DataBufferOffset+UNCOMPRESSED) // allocate memory - copy(buf[0:this.DataBufferOffset], []byte(newHeader)) // copy the new header into the memory - - if iUNCOMPRESSED, err = lz4.UncompressBlock(this.RAW[zStartIndex:], buf[this.DataBufferOffset:]); err != nil { - return err - } - - // Overwrite old Buffer with uncompressed one - this.LEN = uint64(iUNCOMPRESSED) + hLEN - 3 // see: newHeader :=... - this.RAW = buf - - return nil -} - -func (this *Chunk) readUInt64At(offset uint64) (uint64, uint64, uint64, error) { - // Can contain an ext code in the form "val:extval" - if this.RAW == nil { - return 0, 0, 0, errors.New("Nil chunk") - } - - var zero uint64 = uint64('0') - var val uint64 = 0 - var extval uint64 = 0 - var bytesRead uint64 = 0 - var maxLEN int = len(this.RAW) - int(offset) // 0...end of chunk - var isExt bool = false - - if maxLEN < 0 { - maxLEN = 0 - } - if maxLEN > 41 { - maxLEN = 41 - } // MaxInt64 = 18446744073709551615 (len=20) 18446744073709551615:18446744073709551615 (len=41) - - for { - if bytesRead == uint64(maxLEN) { - return val, extval, bytesRead, nil - } - switch c := this.RAW[bytesRead+offset]; c { - case ':': - isExt = true - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - if isExt { - extval = extval*10 + (uint64(c) - zero) - } else { - val = val*10 + (uint64(c) - zero) - } - case ' ': - return val, extval, bytesRead + 1, nil - default: - return 0, 0, 0, errors.New("Invalid rune") - } - bytesRead++ - } - return 0, 0, 0, errors.New("Overflow") -} - -func (this *Chunk) readValueAt(offset uint64) (Value, uint64, error) { - value := Value{Type: this.RAW[offset], Buffer: nil} - switch bytesRead, err := value.readBufferAt(this, offset+1); { - case err != nil: - return Value{Type: 0, Buffer: nil}, 0, err - case bytesRead == 0: - return Value{Type: 0, Buffer: nil}, 0, errors.New("End Of Chunk") - default: - return value, 1 + bytesRead, nil - } -} - -func (this *Value) readBufferAt(chunk *Chunk, offset uint64) (uint64, error) { - var bytesLeft uint64 = 0 - if chunk.GetChunkSize() > offset { - bytesLeft = chunk.GetChunkSize() - offset - } - this.Buffer = nil - - switch this.Type { - case CMD_NULL: - return 1, nil - - case CMD_STRING, CMD_ZEROSTRING, CMD_ERROR, CMD_INT, CMD_FLOAT, CMD_BLOB, CMD_JSON, CMD_COMMAND, CMD_RECONNECT, CMD_PUBSUB: - var TRIM uint64 = 0 // Trims if it is a the C-String - - switch this.Type { - case CMD_INT: // Space terminated INT - // MaxInt64 = 18446744073709551615 = 20 bytes - // MinInt64 = -9223372036854775807 = 20 bytes <- MAX LEN - if bytesLeft > 20 { - bytesLeft = 20 - } - fallthrough - - case CMD_FLOAT: // Space terminated FLOAT - // MaxFloat32 = 3.40282346638528859811704183484516925440e+38 = 44 bytes - // SmalestFloat32 = 1.401298464324817070923729583289916131280e-45 = 45 bytes - // FaxFloat64 = 1.79769313486231570814527423731704356798070e+308 = 48 bytes - // SmallestFloat64 = 4.9406564584124654417656879286822137236505980e-324 = 50 bytes <- MAX LEN - if bytesLeft > 50 { - bytesLeft = 50 - } - - bytesRead := uint64(0) - for ; bytesRead < bytesLeft; bytesRead++ { - if chunk.RAW[offset+bytesRead] == ' ' { - bytesRead++ - break - } - this.Buffer = chunk.RAW[offset : offset+1+bytesRead] - } - if len(this.Buffer) == 0 { - return 0, errors.New("End Of Chunk") - } - return bytesRead, nil - - case CMD_ZEROSTRING: // Zero terminated C-String - TRIM = 1 // Cut one byte off the buffer / dont copy the zero byte of the C string - fallthrough - - default: // Everything else is a LEN Value (+!-$#^@) - switch LEN, _, len, err := chunk.readUInt64At(offset); { - case err != nil: - return 0, err - case len == 0: - return 0, errors.New("LEN not found") - default: - this.Buffer = chunk.RAW[offset+len : offset+len+LEN-TRIM] - return len + LEN, nil - } - } - } - return 0, errors.New("Unsuported type") -} - -func protocolBufferFromValue(v interface{}) [][]byte { - if v == nil { - return protocolBufferFromNull() - } else { - switch v.(type) { - case int, int8, int16, int32, int64: - return protocolBufferFromInt(v) - case float32, float64: - return protocolBufferFromFloat(v) - case string: - return protocolBufferFromString(v.(string), true) - case []byte: - return protocolBufferFromBytes(v.([]byte)) - default: - return make([][]byte, 0) - } - } -} - -func protocolBufferFromNull() [][]byte { - return [][]byte{[]byte(fmt.Sprintf("%c ", CMD_NULL))} -} - -func protocolBufferFromString(v string, nullterminated bool) [][]byte { - if nullterminated { - return [][]byte{[]byte(fmt.Sprintf("%c%d %s\000", CMD_ZEROSTRING, len(v)+1, v))} - } else { - return [][]byte{[]byte(fmt.Sprintf("%c%d %s", CMD_STRING, len(v), v))} - } -} - -func protocolBufferFromInt(v interface{}) [][]byte { - return [][]byte{[]byte(fmt.Sprintf("%c%v ", CMD_INT, v))} -} - -func protocolBufferFromFloat(v interface{}) [][]byte { - return [][]byte{[]byte(fmt.Sprintf("%c%v ", CMD_FLOAT, v))} -} - -// func protocolBufferFromFloat(v interface{}) [][]byte { -// stringrep := fmt.Sprintf("%v", v) -// return [][]byte{[]byte( fmt.Sprintf( "%c%d %s", CMD_STRING, len(stringrep), stringrep ) )} -// } - -func protocolBufferFromBytes(v []byte) [][]byte { - header := []byte(fmt.Sprintf("%c%d ", CMD_BLOB, len(v))) - return [][]byte{header, v} -} - -func (this *SQCloud) sendString(data string) (int, error) { - var err error - var bytesSent int - var bytesToSend int - - if err := this.reconnect(); err != nil { - return 0, err - } - switch this.Timeout { - case 0: - if err := (*this.sock).SetWriteDeadline(time.Time{}); err != nil { - return 0, err - } - default: - if err := (*this.sock).SetWriteDeadline(time.Now().Add(this.Timeout)); err != nil { - return 0, err - } - } - - rawBuffer := protocolBufferFromString(data, false)[0] - bytesToSend = len(rawBuffer) - - if bytesSent, err = (*this.sock).Write(rawBuffer); err != nil { - return bytesSent, err - } - if bytesSent != bytesToSend { - return bytesSent, errors.New("Partitial data sent") - } - - return bytesSent, nil -} - -func (this *SQCloud) sendBytes(data []byte) (int, error) { - var err error - var bytesSent int - var bytesToSend int - - if err := this.reconnect(); err != nil { - return 0, err - } - switch this.Timeout { - case 0: - if err := (*this.sock).SetWriteDeadline(time.Time{}); err != nil { - return 0, err - } - default: - if err := (*this.sock).SetWriteDeadline(time.Now().Add(this.Timeout)); err != nil { - return 0, err - } - } - - header := []byte(fmt.Sprintf("%c%d ", CMD_BLOB, len(data))) - bytesToSend = len(header) - - if bytesSent, err = (*this.sock).Write(header); err != nil { - return bytesSent, err - } - if bytesSent != bytesToSend { - return bytesSent, errors.New("Partitial data sent") - } - - bytesToSend = len(data) - if bytesToSend > 0 { - if bytesSent, err = (*this.sock).Write(data); err != nil { - return bytesSent, err - } - if bytesSent != bytesToSend { - return bytesSent, errors.New("Partitial data sent") - } - } else { - bytesSent = 0 - } - - return bytesSent, nil -} - -func (this *SQCloud) sendArray(command string, values []interface{}) (int, error) { - var err error - var bytesSent int - var bytesToSend int - - // prepare the connection - if err := this.reconnect(); err != nil { - return 0, err - } - switch this.Timeout { - case 0: - if err := (*this.sock).SetWriteDeadline(time.Time{}); err != nil { - return 0, err - } - default: - if err := (*this.sock).SetWriteDeadline(time.Now().Add(this.Timeout)); err != nil { - return 0, err - } - } - - // convert values to buffers encoded with whe sqlitecloud protocol - buffers := [][]byte{protocolBufferFromString(command, true)[0]} - for _, v := range values { - buffers = append(buffers, protocolBufferFromValue(v)...) - } - - // calculate the array header - totsize := 0 - for _, b := range buffers { - totsize += len(b) - } - // the number of the array object must include the command - n := len(values) + 1 - lenarrayrep := fmt.Sprintf("%d ", n) - totsize += len(lenarrayrep) - header := []byte(fmt.Sprintf("%c%d %s", CMD_ARRAY, totsize, lenarrayrep)) - - // send the header - bytesToSend = len(header) - // fmt.Printf("Write buffer(%d): %v\n", bytesToSend, header) - if bytesSent, err = (*this.sock).Write(header); err != nil { - return bytesSent, err - } - if bytesSent != bytesToSend { - return bytesSent, errors.New("Partitial data sent") - } - - // send each buffer - for _, data := range buffers { - bytesToSend = len(data) - if bytesToSend > 0 { - // fmt.Printf("Write buffer(%d): %v\n", bytesToSend, data) - if bytesSent, err = (*this.sock).Write(data); err != nil { - return bytesSent, err - } - if bytesSent != bytesToSend { - return bytesSent, errors.New("Partitial data sent") - } - } else { - bytesSent = 0 - } - } - - return 0, nil -} - -func (this *SQCloud) readNextRawChunk() (*Chunk, error) { - // every chunk (except RAW JSON) starts with: ()[data]_ - // (_=space) - - NULL := Chunk{0, 0, []byte{'_', ' '}} - chunk := NULL - snoop := []byte{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} - - // Read first byte = Chunk Type - switch this.Timeout { - case 0: - if err := (*this.sock).SetReadDeadline(time.Time{}); err != nil { - return &NULL, err - } - default: - if err := (*this.sock).SetReadDeadline(time.Now().Add(this.Timeout)); err != nil { - return &NULL, err - } - } - switch readCount, err := (*this.sock).Read(snoop[0:1]); { - - case err == io.EOF: - return &NULL, errors.New("EOF") - case err != nil: - if netErr, ok := err.(net.Error); ok && netErr.Timeout() { - return &NULL, errors.New("Timeout") - } else { - return &NULL, err - } - case readCount < 1: - return &NULL, errors.New("No Data") - - case snoop[0] == '{': - return &NULL, errors.New("Not implmented") ///////////////////// <<< This blocks RAW JSON - - default: - // Reading second argument (NULL, INT/FLOAT, LEN) until first space - for tokenLength := 1; tokenLength < len(snoop); tokenLength++ { - - switch this.Timeout { - case 0: - if err := (*this.sock).SetReadDeadline(time.Time{}); err != nil { - return &NULL, err - } - default: - if err := (*this.sock).SetReadDeadline(time.Now().Add(this.Timeout)); err != nil { - return &NULL, err - } - } - switch readCount, err := (*this.sock).Read(snoop[tokenLength : tokenLength+1]); { - - case err == io.EOF: - return &NULL, errors.New("EOF") - case err != nil: - if netErr, ok := err.(net.Error); ok && netErr.Timeout() { - return &NULL, errors.New("Timeout") - } else { - return &NULL, err - } - case readCount < 1: - return &NULL, errors.New("No Data") - - case snoop[tokenLength] == ' ': // first space found, raw header = complete - - chunk.DataBufferOffset = 0 - - switch snoop[0] { - case '_': // SCSP NULL - chunk.LEN = 0 - chunk.RAW = snoop[0 : tokenLength+1] - return &chunk, nil - - case ':', ',': // SCSP Integer, SCSP Float - chunk.LEN = uint64(tokenLength) - chunk.RAW = snoop[0 : tokenLength+1] - return &chunk, nil - - case '#': // SCSP JSON - fallthrough - - default: // all other - except JSON RAW - var LEN uint64 - if LEN, err = strconv.ParseUint(string(snoop[1:tokenLength]), 10, 64); err != nil { - return &NULL, err - } - - tokenLength++ - chunk.DataBufferOffset = uint64(tokenLength) - chunk.RAW = make([]byte, uint64(tokenLength)+LEN) - - // Copy the static snoop buffer into the new dynamic data buffer - copy(chunk.RAW[0:tokenLength], snoop[0:tokenLength]) - - var totalBytesRead uint64 = 0 - for { - - switch this.Timeout { - case 0: - if err := (*this.sock).SetReadDeadline(time.Time{}); err != nil { - return &NULL, err - } - default: - if err := (*this.sock).SetReadDeadline(time.Now().Add(this.Timeout)); err != nil { - return &NULL, err - } - } - switch readCount, err := (*this.sock).Read(chunk.RAW[uint64(tokenLength)+totalBytesRead:]); { - - case err == io.EOF: - totalBytesRead += uint64(readCount) - if totalBytesRead == LEN { - return &chunk, nil - } - return &NULL, errors.New("EOF") - case err != nil: - if netErr, ok := err.(net.Error); ok && netErr.Timeout() { - return &NULL, errors.New("Timeout") - } else { - return &NULL, err - } - case totalBytesRead+uint64(readCount) == LEN: - chunk.LEN = LEN - return &chunk, nil - default: - totalBytesRead += uint64(readCount) - } - } - } - } - } - return &NULL, errors.New("Snoop overflow") - } -} diff --git a/GO/sdk/connection.go b/GO/sdk/connection.go deleted file mode 100644 index 550a88de..00000000 --- a/GO/sdk/connection.go +++ /dev/null @@ -1,635 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud GO SDK -// /// /// /// Version : 1.1.2 -// // /// /// /// Date : 2021/10/13 -// /// /// /// /// Author : Andreas Pfeil -// /// /// /// /// -// /// ////////// /// /// Description : Go Methods related to the -// //// /// /// SQCloud class for managing -// //// ////////// /// the connection and executing -// //// //// queries. -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -package sqlitecloud - -import ( - "crypto/tls" - "crypto/x509" - "errors" - "fmt" - "io/ioutil" - "net" - "os" - "strconv" - "strings" - "time" - - "github.com/xo/dburl" -) - -type SQCloudConfig struct { - Host string - Port int - Username string - Password string - Database string - Timeout time.Duration - CompressMode string - Secure bool - Pem string - ApiKey string - NoBlob bool // flag to tell the server to not send BLOB columns - MaxData int // value to tell the server to not send columns with more than max_data bytes - MaxRows int // value to control rowset chunks based on the number of rows - MaxRowset int // value to control the maximum allowed size for a rowset -} - -type SQCloud struct { - SQCloudConfig - - sock *net.Conn - - psub *SQCloud - // psubc chan string - Callback func(*SQCloud, string) - - cert *tls.Config - - uuid string // 36 runes -> remove maybe???? - secret string // 36 runes -> remove maybe???? - - ErrorCode int - ExtErrorCode int - ErrorOffset int - ErrorMessage string -} - -const SQLiteCloudCA = "SQLiteCloudCA" - -func New(config SQCloudConfig) *SQCloud { - return &SQCloud{SQCloudConfig: config} -} - -// init registers the sqlitecloud scheme in the connection steing parser. -func init() { - dburl.Register(dburl.Scheme{ - Driver: "sc", // sqlitecloud - Generator: dburl.GenFromURL("sqlitecloud://user:pass@host.com:8860/dbname?timeout=10&compress=NO&tls=INTERN"), - Transport: dburl.TransportTCP, - Opaque: false, - Aliases: []string{"sqlitecloud"}, - Override: "", - }) -} - -// Helper functions - -// ParseConnectionString parses the given connection string and returns it's components. -// An empty string ("") or -1 is returned as the corresponding return value, if a component of the connection string was not present. -// No plausibility checks are done (see: CheckConnectionParameter). -// If the connection string could not be parsed, an error is returned. -func ParseConnectionString(ConnectionString string) (config *SQCloudConfig, err error) { - u, err := dburl.Parse(ConnectionString) - if err == nil { - config = &SQCloudConfig{} - - config.Host = u.Hostname() - config.Port = 0 - config.Username = u.User.Username() - config.Password, _ = u.User.Password() - config.Database = strings.TrimPrefix(u.Path, "/") - config.Timeout = 0 - config.CompressMode = "NO" - config.Secure = true - config.Pem = "" - config.ApiKey = "" - config.NoBlob = false - config.MaxData = 0 - config.MaxRows = 0 - config.MaxRowset = 0 - - sPort := strings.TrimSpace(u.Port()) - if len(sPort) > 0 { - if config.Port, err = strconv.Atoi(sPort); err != nil { - return nil, err - } - } - - for key, values := range u.Query() { - lastLiteral := strings.TrimSpace(values[len(values)-1]) - switch strings.ToLower(strings.TrimSpace(key)) { - case "timeout": - if timeout, err := strconv.Atoi(lastLiteral); err != nil { - return nil, err - } else { - config.Timeout = time.Duration(timeout) * time.Second - } - - case "compress": - config.CompressMode = strings.ToUpper(lastLiteral) - case "tls": - config.Secure, config.Pem = ParseTlsString(lastLiteral) - case "apikey": - config.ApiKey = lastLiteral - case "noblob": - if b, err := parseBool(lastLiteral, config.NoBlob); err == nil { - config.NoBlob = b - } - case "maxdata": - if v, err := strconv.Atoi(lastLiteral); err == nil { - config.MaxData = v - } - case "maxrows": - if v, err := strconv.Atoi(lastLiteral); err == nil { - config.MaxRows = v - } - case "maxrowset": - if v, err := strconv.Atoi(lastLiteral); err == nil { - config.MaxRowset = v - } - } - } - - // fmt.Printf( "NO ERROR: Host=%s, Port=%d, User=%s, Password=%s, Database=%s, Timeout=%d, Compress=%s\r\n", host, port, user, pass, database, timeout, compress ) - return config, nil - } - return nil, err -} - -func ParseTlsString(tlsconf string) (secure bool, pem string) { - switch strings.ToUpper(strings.TrimSpace(tlsconf)) { - case "", "0", "N", "NO", "FALSE", "OFF", "DISABLE", "DISABLED": - return false, "" - case "1", "Y", "YES", "TRUE", "ON", "ENABLE", "ENABLED": - return true, "" - case strings.ToUpper(SQLiteCloudCA), "INTERN", "": - return true, SQLiteCloudCA - default: - return true, strings.TrimSpace(tlsconf) - } -} - -// CheckConnectionParameter checks the given connection arguments for validly. -// Host is either a resolve able hostname or an IP address. -// Port is an unsigned int number between 1 and 65535. -// Timeout must be 0 (=no timeout) or a positive number. -// Compress must be "NO" or "LZ4". -// Username, Password and Database are ignored. -// If a given value does not fulfill the above criteria's, an error is returned. -func (this *SQCloud) CheckConnectionParameter() error { - - if strings.TrimSpace(this.Host) == "" { - return errors.New(fmt.Sprintf("Invalid hostname (%s)", this.Host)) - } - - ip := net.ParseIP(this.Host) - if ip == nil { - if _, err := net.LookupHost(this.Host); err != nil { - return errors.New(fmt.Sprintf("Can't resolve hostname (%s)", this.Host)) - } - } - - if this.Port == 0 { - this.Port = 8860 - } - if this.Port < 1 || this.Port >= 0xFFFF { - return errors.New(fmt.Sprintf("Invalid Port (%d)", this.Port)) - } - - // if this.Timeout == 0 { - // this.Timeout = 10 * time.Second - // } - if this.Timeout < 0 { - return errors.New(fmt.Sprintf("Invalid Timeout (%s)", this.Timeout.String())) - } - - switch strings.ToUpper(this.CompressMode) { - case "NO", "LZ4": - default: - return errors.New(fmt.Sprintf("Invalid compression method (%s)", this.CompressMode)) - } - - if this.Secure { - var pool *x509.CertPool = nil - pem := []byte{} - - switch _, trimmed := ParseTlsString(this.Pem); trimmed { - case "": - break - case SQLiteCloudCA: - pem = []byte(SqliteCloudCAPEM) - default: - // check if it is a filepath - _, err := os.Stat(trimmed) - if os.IsNotExist(err) { - // not a filepath, use the string as a pem string - pem = []byte(trimmed) - } else { - // its a file, read its content into the pem string - switch bytes, err := ioutil.ReadFile(trimmed); { - case err != nil: - return errors.New(fmt.Sprintf("Could not open PEM file in '%s'", trimmed)) - default: - pem = bytes - } - } - } - - if len(pem) > 0 { - pool = x509.NewCertPool() - - if !pool.AppendCertsFromPEM(pem) { - return errors.New(fmt.Sprintf("Could not append certs from PEM")) - } - } - - this.cert = &tls.Config{ - RootCAs: pool, - InsecureSkipVerify: false, - MinVersion: tls.VersionTLS12, - } - } - - return nil -} - -// Creation - -// reset resets all Connection attributes. -func (this *SQCloud) reset() { - _ = this.Close() - this.uuid = "" - this.secret = "" - this.resetError() -} - -// Connect creates a new connection and tries to connect to the server using the given connection string. -// The given connection string is parsed and checked for correct parameters. -// Nil and an error is returned if the connection string had invalid values or a connection to the server could not be established, -// otherwise, a pointer to the newly established connection is returned. -func Connect(ConnectionString string) (*SQCloud, error) { - config, err := ParseConnectionString(ConnectionString) - - if err != nil { - return nil, err - } - - connection := &SQCloud{SQCloudConfig: *config} - - if err = connection.Connect(); err != nil { - _ = connection.Close() - return nil, err - } else { - err = connection.Compress(connection.CompressMode) - return connection, err - } -} - -// Connection Functions - -// Connect connects to a SQLite Cloud server instance using the given arguments. -// If Connect is called on an already established connection, the old connection is closed first. -// All arguments are checked for valid values (see: CheckConnectionParameter). -// invalid argument values where given or the connection could not be established. -func (this *SQCloud) Connect() error { - this.reset() // also closes an open connection - - switch err := this.CheckConnectionParameter(); { - case err != nil: - return err - default: - return this.reconnect() - } -} - -// reconnect closes and then reopens a connection to the SQLite Cloud database server. -func (this *SQCloud) reconnect() error { - if this.sock != nil { - return nil - } - - this.resetError() - - var dialer = net.Dialer{} - dialer.Timeout = this.Timeout - dialer.DualStack = true - - switch { - case this.cert != nil: - if tls_c, err := tls.DialWithDialer(&dialer, "tcp", net.JoinHostPort(this.Host, strconv.Itoa(this.Port)), this.cert); err != nil { - this.ErrorCode = -1 - this.ErrorMessage = err.Error() - return err - } else { - c := net.Conn(tls_c) - this.sock = &c - } - default: - // todo: use the dialer... - if c, err := net.DialTimeout("tcp", net.JoinHostPort(this.Host, strconv.Itoa(this.Port)), this.Timeout); err != nil { - this.ErrorCode = -1 - this.ErrorMessage = err.Error() - return err - } else { - this.sock = &c - } - } - - commands := "" - args := []interface{}{} - - if strings.TrimSpace(this.Username) != "" { - c, a := authCommand(this.Username, this.Password) - commands += c - args = append(args, a...) - - } else if strings.TrimSpace(this.ApiKey) != "" { - c, a := authWithKeyCommand(this.ApiKey) - commands += c - args = append(args, a...) - } - - if strings.TrimSpace(this.Database) != "" { - c, a := useDatabaseCommand(this.Database) - commands += c - args = append(args, a...) - } - - if this.NoBlob { - commands += noblobCommand(this.NoBlob) - } - - if this.MaxData > 0 { - commands += maxdataCommand(this.MaxData) - } - - if this.MaxRows > 0 { - commands += maxrowsCommand(this.MaxRows) - } - - if this.MaxRowset > 0 { - commands += maxrowsetCommand(this.MaxRowset) - } - - if commands != "" { - if len(args) > 0 { - if err := this.ExecuteArray(commands, args); err != nil { - return err - } - } else { - if err := this.Execute(commands); err != nil { - return err - } - } - } - - return nil -} - -// Close closes the connection to the SQLite Cloud Database server. -// The connection can later be reopened (see: reconnect) -func (this *SQCloud) Close() error { - var err_sock, err_psub error - - err_psub = this.psubClose() - - if this.sock != nil { - err_sock = (*this.sock).Close() - } - this.sock = nil - - this.resetError() - - if err_sock != nil { - this.setError(-1, err_sock.Error()) - return err_sock - } - - if err_psub != nil { - this.setError(-1, err_psub.Error()) - return err_psub - } - return nil -} - -func noblobCommand(NoBlob bool) string { - if NoBlob { - return "SET CLIENT KEY NOBLOB TO 1;" - } else { - return "SET CLIENT KEY NOBLOB TO 0;" - } -} - -func maxdataCommand(v int) string { - return fmt.Sprintf("SET CLIENT KEY MAXDATA TO %d;", v) -} - -func maxrowsCommand(v int) string { - return fmt.Sprintf("SET CLIENT KEY MAXROWS TO %d;", v) -} - -func maxrowsetCommand(v int) string { - return fmt.Sprintf("SET CLIENT KEY MAXROWSET TO %d;", v) -} - -func compressCommand(CompressMode string) string { - switch compression := strings.ToUpper(CompressMode); { - case compression == "NO": - return "SET CLIENT KEY COMPRESSION TO 0;" - case compression == "LZ4": - return "SET CLIENT KEY COMPRESSION TO 1;" - default: - return "" - } -} - -// Compress enabled or disables data compression for this connection. -// If enabled, the data is compressed with the LZ4 compression algorithm, otherwise no compression is applied the data. -func (this *SQCloud) Compress(CompressMode string) error { - switch c := compressCommand(CompressMode); { - case this.sock == nil: - return errors.New("Not connected") - case c == "": - return errors.New(fmt.Sprintf("Invalid method (%s)", CompressMode)) - default: - return this.Execute(c) - } -} - -// IsConnected checks the connection to the SQLite Cloud database server by sending a PING command. -// true is returned, if the connection is established and actually working, false otherwise. -func (this *SQCloud) IsConnected() bool { - switch { - case this.sock == nil: - return false - case this.Ping() != nil: - return false - default: - return true - } -} - -// Error Methods - -func (this *SQCloud) setError(ErrorCode int, ErrorMessage string) { - this.ErrorCode = ErrorCode - this.ErrorMessage = ErrorMessage -} - -// resetError resets the error code and message of the last run command. -func (this *SQCloud) resetError() { this.setError(0, "") } - -// GetErrorCode returns the error code of the last unsuccessful command as an int value. -// 0 is returned if the last command run successful. -func (this *SQCloud) GetErrorCode() int { return this.ErrorCode } - -// GetExtErrorCode returns the error code of the last unsuccessful command as an int value. -// 0 is returned if the last command run successful. -func (this *SQCloud) GetExtErrorCode() int { return this.ExtErrorCode } - -// GetErrorOffset returns the error code of the last unsuccessful command as an int value. -// 0 is returned if the last command run successful. -func (this *SQCloud) GetErrorOffset() int { return this.ErrorOffset } - -// IsError checks the successful execution of the last method call / command. -// true is returned if the last command resulted in an error, false otherwise. -func (this *SQCloud) IsError() bool { return this.GetErrorCode() != 0 } - -// GetErrorMessage returns the error message of the last unsuccuessful command as an error. -// nil is returned if the last command run successful. -func (this *SQCloud) GetErrorMessage() error { - switch this.IsError() { - case true: - return errors.New(this.ErrorMessage) - default: - return nil - } -} - -// GetError returned the error code and message of the last unsuccessful command. -// 0 and nil is returned if the last command run successful. -func (this *SQCloud) GetError() (int, int, int, error) { - return this.GetErrorCode(), this.GetExtErrorCode(), this.GetErrorOffset(), this.GetErrorMessage() -} - -// Data Access Functions - -// Select executes a query on an open SQLite Cloud database connection. -// If an error occurs during the execution of the query, nil and an error describing the problem is returned. -// On successful execution, a pointer to the result is returned. -func (this *SQCloud) Select(SQL string) (*Result, error) { - this.resetError() - - if _, err := this.sendString(SQL); err != nil { - this.ErrorCode = 100003 - this.ErrorMessage = fmt.Sprintf("Internal Error: sendString (%s)", err.Error()) - return nil, errors.New(this.ErrorMessage) - } - - switch result, err := this.readResult(); { - case result == nil: - return nil, errors.New("nil") - - case result.IsError(): - this.ErrorCode, this.ExtErrorCode, this.ErrorOffset, this.ErrorMessage, _ = result.GetError() - result.Free() - return nil, errors.New(this.ErrorMessage) - - case err != nil: - this.ErrorCode, this.ExtErrorCode, this.ErrorOffset, this.ErrorMessage = 100000, NO_EXTCODE, NO_OFFCODE, err.Error() - result.Free() - return nil, err - - default: - return result, nil - } -} - -func (this *SQCloud) SelectArray(SQL string, values []interface{}) (*Result, error) { - this.resetError() - - if _, err := this.sendArray(SQL, values); err != nil { - this.ErrorCode = 100003 - this.ErrorMessage = fmt.Sprintf("Internal Error: sendArray (%s)", err.Error()) - return nil, errors.New(this.ErrorMessage) - } - - switch result, err := this.readResult(); { - case result == nil: - return nil, errors.New("nil") - - case result.IsError(): - this.ErrorCode, this.ExtErrorCode, this.ErrorOffset, this.ErrorMessage, _ = result.GetError() - result.Free() - return nil, errors.New(this.ErrorMessage) - - case err != nil: - this.ErrorCode, this.ExtErrorCode, this.ErrorOffset, this.ErrorMessage = 100000, NO_EXTCODE, NO_OFFCODE, err.Error() - result.Free() - return nil, err - - default: - return result, nil - } -} - -func (this *SQCloud) SendBlob(data []byte) error { - this.resetError() - - if _, err := this.sendBytes(data); err != nil { - this.ErrorCode = 100003 - this.ErrorMessage = fmt.Sprintf("Internal Error: sendBytes (%s)", err.Error()) - return errors.New(this.ErrorMessage) - } - - switch result, err := this.readResult(); { - case result == nil: - return errors.New("nil") - - case result.IsError(): - this.ErrorCode, this.ExtErrorCode, this.ErrorOffset, this.ErrorMessage, _ = result.GetError() - result.Free() - return errors.New(this.ErrorMessage) - - case err != nil: - this.ErrorCode, this.ExtErrorCode, this.ErrorOffset, this.ErrorMessage = 100000, NO_EXTCODE, NO_OFFCODE, err.Error() - result.Free() - return err - - default: - return nil - } -} - -// Execute executes the given query. -// If the execution was not successful, an error describing the reason of the failure is returned. -func (this *SQCloud) Execute(SQL string) error { - if result, err := this.Select(SQL); result != nil { - defer result.Free() - - if !result.IsOK() { - return errors.New("ERROR: Unexpected Result (-1)") - } - return err - } else { - return err - } -} - -func (this *SQCloud) ExecuteArray(SQL string, values []interface{}) error { - if result, err := this.SelectArray(SQL, values); result != nil { - defer result.Free() - - if !result.IsOK() { - return errors.New("ERROR: Unexpected Result (-1)") - } - return err - } else { - return err - } -} diff --git a/GO/sdk/go.mod b/GO/sdk/go.mod deleted file mode 100644 index 77fbc2cd..00000000 --- a/GO/sdk/go.mod +++ /dev/null @@ -1,14 +0,0 @@ -module github.com/sqlitecloud/sdk - -go 1.18 - -require ( - github.com/pierrec/lz4 v2.6.1+incompatible - github.com/xo/dburl v0.12.4 - golang.org/x/term v0.1.0 -) - -require ( - github.com/frankban/quicktest v1.14.3 // indirect - golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect -) diff --git a/GO/sdk/go.sum b/GO/sdk/go.sum deleted file mode 100644 index 54c0e11f..00000000 --- a/GO/sdk/go.sum +++ /dev/null @@ -1,26 +0,0 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= -github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/xo/dburl v0.12.4 h1:mAIQjCNqCRtfytZNN0tZzK01rfng3n4Ei1s+H9lh61I= -github.com/xo/dburl v0.12.4/go.mod h1:K6rSPgbVqP3ZFT0RHkdg/M3M5KhLeV2MaS/ZqaLd1kA= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= diff --git a/GO/sdk/helper.go b/GO/sdk/helper.go deleted file mode 100644 index c45d337e..00000000 --- a/GO/sdk/helper.go +++ /dev/null @@ -1,141 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud GO SDK -// /// /// /// Version : 1.0.0 -// // /// /// /// Date : 2021/08/31 -// /// /// /// /// Author : Andreas Pfeil -// /// /// /// /// -// /// ////////// /// /// Description : GO Functions for parsing -// //// /// /// SQLite Cloud values and -// //// ////////// /// enquoting strings. -// //// //// -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -package sqlitecloud - -import ( - "errors" - "fmt" - "strconv" - "strings" -) - -// Helper functions - -func escapeString(s, quote string) string { - repeatedquote := quote + quote - s = strings.Replace(s, repeatedquote, quote, -1) - s = strings.Replace(s, quote, repeatedquote, -1) - return s -} - -// SQCloudEnquoteString enquotes the given string if necessary and returns the result as a news created string. -// If the given string contains a '"', the '"' is properly escaped. -// If the given string contains one or more spaces, the whole string is enquoted with '"' -func SQCloudEnquoteString(s string) string { - s = strings.TrimSpace(s) - - singlequote := "'" - doublequote := "\"" - - if !strings.Contains(s, singlequote) && !strings.Contains(s, doublequote) { - if strings.Contains(s, " ") { - return fmt.Sprintf("'%s'", s) - } - - return s - } - - if l := len(s); l > 1 { - for _, q := range []string{singlequote, doublequote} { - if strings.HasPrefix(s, q) && strings.HasSuffix(s, q) { - unquoted := s[1 : l-1] - if !strings.Contains(unquoted, q) { - return s - } - return q + escapeString(unquoted, q) + q - } - } - } - - for _, q := range []string{singlequote, doublequote} { - if !strings.Contains(s, q) { - return q + s + q - } - } - - return singlequote + escapeString(s, singlequote) + singlequote -} - -/* -// SQCloudEnquoteString enquotes the given string if necessary and returns the result as a news created string. -// If the given string contains a '"', the '"' is properly escaped. -// If the given string contains one or more spaces, the whole string is enquoted with '"' -func SQCloudEnquoteString(Token string) string { - Token = strings.Replace(Token, "\"", "\"\"", -1) - Token = strings.Replace(Token, "'", "''", -1) - if strings.Contains(Token, " ") { - return fmt.Sprintf("\"%s\"", Token) - } - return Token -} -*/ - -// parseBool parses the given string value and tries to interpret the value as a bool. -// true is returned if the value is "TRUE", "ENABLED" or 1. -// false is returned if the value is "FALSE", "DISABLED" or 0. -// The specified defaultValue is returned, if the given string value was an emptry string. -// An error is returned in any other case. -func parseBool(value string, defaultValue bool) (bool, error) { - switch strings.ToUpper(strings.TrimSpace(value)) { - case "FALSE", "DISABLED", "0": - return false, nil - case "TRUE", "ENABLED", "1": - return true, nil - case "": - return defaultValue, nil - default: - return false, errors.New("ERROR: Not a Boolean value") - } -} - -// parseInt parses the given string value and tries to extract its value as an int value. -// If value was an empty string, the defaultValue is evaluated instead. -// If the given string value does not resemble a numeric value or its numeric value is smaler than minValue or exceeds maxValue, an error describing the problem is returned. -func parseInt(value string, defaultValue int, minValue int, maxValue int) (int, error) { - // println( "ParseInt = " + value ) - value = strings.TrimSpace(value) - if value == "" { - value = fmt.Sprintf("%d", defaultValue) - } - if v, err := strconv.Atoi(value); err == nil { - if v < minValue { - return 0, errors.New("ERROR: The given Number is too small") - } - if v > maxValue { - return 0, errors.New("ERROR: The given Number is too large") - } - return v, nil - } else { - return 0, err - } -} - -// parseString returns a non empty string. -// The given string value is trimmed. -// If the given string is an empty string, the defaultValue is evaluated instead. -// If the given string and the defaultValue are emptry strings, an error is returned. -func parseString(value string, defaultValue string) (string, error) { - value = strings.TrimSpace(value) - if value == "" { - value = strings.TrimSpace(defaultValue) - } - if value == "" { - return "", errors.New("ERROR: Empty value") - } - return value, nil -} diff --git a/GO/sdk/pem.go b/GO/sdk/pem.go deleted file mode 100644 index adba654c..00000000 --- a/GO/sdk/pem.go +++ /dev/null @@ -1,42 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud GO SDK -// /// /// /// Version : 1.0.0 -// // /// /// /// Date : 2021/09/02 -// /// /// /// /// Author : Andreas Pfeil -// /// /// /// /// -// /// ////////// /// /// Description : Default client certificate. -// //// /// /// This is a public key. -// //// ////////// /// -// //// //// -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -package sqlitecloud - -const SqliteCloudCAPEM = `-----BEGIN CERTIFICATE----- -MIID6zCCAtOgAwIBAgIUI0lTm5CfVf3mVP8606CkophcyB4wDQYJKoZIhvcNAQEL -BQAwgYQxCzAJBgNVBAYTAklUMQswCQYDVQQIDAJNTjEQMA4GA1UEBwwHVmlhZGFu -YTEbMBkGA1UECgwSU1FMaXRlIENsb3VkLCBJbmMuMRQwEgYDVQQDDAtTUUxpdGVD -bG91ZDEjMCEGCSqGSIb3DQEJARYUbWFyY29Ac3FsaXRlY2xvdWQuaW8wHhcNMjEw -ODI1MTAwMTI0WhcNMzEwODIzMTAwMTI0WjCBhDELMAkGA1UEBhMCSVQxCzAJBgNV -BAgMAk1OMRAwDgYDVQQHDAdWaWFkYW5hMRswGQYDVQQKDBJTUUxpdGUgQ2xvdWQs -IEluYy4xFDASBgNVBAMMC1NRTGl0ZUNsb3VkMSMwIQYJKoZIhvcNAQkBFhRtYXJj -b0BzcWxpdGVjbG91ZC5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -ALnqTqnKgXadcZb4bkHWIrF7BEaPzS8ADUMvrmlP4hVOwg6x4rw33aSTfcXSf6/U -6HzqUgW7lu/Qg/O1WyvdTyseCRbopysPLfU3Hg2bOpcP7ZmgsB3xmPm0tXB/QNvb -sHbMGOGvWVKNCTPuemBuMVLAYNyEC5DWAxOG7IVz+arK2/+QeBH0+PLstVTSvUVy -eu1dacjx9kPEWO0gEwgxyYAeTmgYMRSEcicLF7egxoSS2kzUOLyMkWeV92tP+mzC -NKGgQoG4WnSrsE9ZcY3MiIdb0EnN+nR0VOBFejsTyJm7A+Ab6edEuvNmUTbraKJL -jRKZzUt1r4x3GC+j+UVCQp0CAwEAAaNTMFEwHQYDVR0OBBYEFPGRk8InGXhigfm4 -teCLDtYSGu7XMB8GA1UdIwQYMBaAFPGRk8InGXhigfm4teCLDtYSGu7XMA8GA1Ud -EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGPk14p4H6vtJsZdgY2sVS2G -T8Ir4ukue79zRFAoCYfkfSW1A+uZbOK/YIjC1CetMXIde1SGvIMcoo1NjrKiqLls -srUN9SmmLihVzurtnoC5ScoUdRQtae8NBWXJnObxK7uXGBYamfs/x0nq1j7DV6Qc -TkuTmJvkcWGcJ9fBOzHgzi+dV+7Y98LP48Pyj/mAzI2icw+I5+DMzn2IktzFf0G7 -Sjox3HYOoj2uG2669CLAnw6rkHESbi5imasC9FxWBVxWrnNd0icyiDb1wfBc5W9N -otHL5/wB1MaAmCIcQjIxEshj8pSYTecthitmrneimikFf4KFK0YMvGgKrCLmJsg= ------END CERTIFICATE-----` diff --git a/GO/sdk/pubsub.go b/GO/sdk/pubsub.go deleted file mode 100644 index 4a63a295..00000000 --- a/GO/sdk/pubsub.go +++ /dev/null @@ -1,120 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud GO SDK -// /// /// /// Version : 1.0.0 -// // /// /// /// Date : 2021/10/12 -// /// /// /// /// Author : Andreas Pfeil -// /// /// /// /// -// /// ////////// /// /// Description : GO Methods related to the -// //// /// /// SQCloud class for handling -// //// ////////// /// asynchronous communication. -// //// //// -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -/* - REM LISTEN CHANNEL: - REM LISTEN TabName = Listen on WRITES on Table "TabName" in this database (execute 1...n times) - REM LISTEN * = Listen on WRITES on All Tables in this database (execute 1...n times) - REM LISTEN ChanName = Listen on NOTIFYs on the Channel ChanName (execute 1...n times) - - REM UNLISTEN ChanName|TabName = Unregisteres a previous registration - REM UNLISTEN * = Unregisteres ALL (=TabName,*,ChanName) registrations - - REM NOTIFY ChanName = NOTIFY ChanName "" - REM NOTIFY ChanName - - REM LISTEN - 10 SEND "LISTEN *" - 20 RECEIVE "|79 PAUTH a365efef-cfb7-4672-8ed4-45a489ddb194 9230b8d8-93dc-4edc-bcaf-cc118fe32d4d" - 30 IF NO 2.socket IS THERE: OPEN 2.socket - 40 SEND "PAUTH a365efef-cfb7-4672-8ed4-45a489ddb194 9230b8d8-93dc-4edc-bcaf-cc118fe32d4d" - 50 RECEIVE "OK" - 60 START 2.thread - 2.10 IF 2.socket LOST CONNECTION: CLOSE 2.socket and TERMINATE 2.thread - 2.20 RECEIVE "#LEN json" - 2.30 CALL callback_function WITH json - 2.40 GOTO 2.10 - - ON_CLOSE EVENT: - 10 IF 2.socket IS CONNECTED: CLOSE 2.socket - 20 IF main.socket IS CONNECTED: CLOSE main.socket -*/ - -package sqlitecloud - -// GetUUID returns the UUID as string -func (this *SQCloud) GetUUID() string { - return this.uuid // this.CGetCloudUUID() -} - -// psubClose closes the PSUB connection to the SQLite Cloud Database server. -func (this *SQCloud) psubClose() error { - var err error = nil - - if this.psub != nil { - err = (*this.psub).Close() - } - this.psub = nil - - return err -} - -// Creates the specified Channel. -func (this *SQCloud) CreateChannel(Channel string, NoError bool) error { - command := "CREATE CHANNEL ?" - if NoError { - command += " IF NOT EXISTS" - } - return this.ExecuteArray(command, []interface{}{Channel}) -} - -func (this *SQCloud) ListChannels() ([]string, error) { - return this.SelectStringList("LIST CHANNELS") -} - -// Listen subscribes this connection to the specified Channel. -func (this *SQCloud) Listen(Channel string) error { // add a call back function... - return this.ExecuteArray("LISTEN ?", []interface{}{Channel}) -} - -// Listen subscribes this connection to the specified Table. -func (this *SQCloud) ListenTable(TableName string) error { // add a call back function... - return this.ExecuteArray("LISTEN TABLE ?", []interface{}{TableName}) -} - -// Notify sends a wakeup call to the channel Channel -func (this *SQCloud) Notify(Channel string) error { - return this.ExecuteArray("NOTIFY ?", []interface{}{Channel}) -} - -// SendNotificationMessage sends the message Message to the channel Channel -func (this *SQCloud) SendNotificationMessage(Channel string, Message string) error { - return this.ExecuteArray("NOTIFY ? ?", []interface{}{Channel, Message}) -} - -// Unlisten unsubsribs this connection from the specified Channel. -func (this *SQCloud) Unlisten(Channel string) error { - return this.ExecuteArray("UNLISTEN ?", []interface{}{Channel}) -} - -// Unlisten unsubsribs this connection from the specified Table. -func (this *SQCloud) UnlistenTable(TableName string) error { - return this.ExecuteArray("UNLISTEN TABLE ?", []interface{}{TableName}) -} - -// Deletes the specified Channel. -func (this *SQCloud) RemoveChannel(Channel string) error { - return this.ExecuteArray("REMOVE CHANNEL ?", []interface{}{Channel}) -} - -// PAuth returns the auth details for pubsub -func (this *SQCloud) GetPAuth() (string, string) { - if this.psub == nil { - return "", "" - } - return this.psub.uuid, this.psub.secret -} diff --git a/GO/sdk/result.go b/GO/sdk/result.go deleted file mode 100644 index fab5c791..00000000 --- a/GO/sdk/result.go +++ /dev/null @@ -1,1034 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud GO SDK -// /// /// /// Version : 1.2.2 -// // /// /// /// Date : 2021/10/14 -// /// /// /// /// Author : Andreas Pfeil -// /// /// /// /// -// /// ////////// /// /// Description : GO Methods related to the -// //// /// /// Result class. -// //// ////////// /// -// //// //// -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -package sqlitecloud - -import ( - "bufio" - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "strings" - "time" - - "golang.org/x/term" -) - -var rowsetChunkEndPatterns = []([]byte){[]byte("5 0 0 0"), []byte("6 0 0 0 "), []byte("8 0 0 0 0 ")} - -const OUTFORMAT_LIST = 0 -const OUTFORMAT_CSV = 1 -const OUTFORMAT_QUOTE = 2 -const OUTFORMAT_TABS = 3 -const OUTFORMAT_LINE = 4 -const OUTFORMAT_JSON = 5 -const OUTFORMAT_HTML = 6 -const OUTFORMAT_MARKDOWN = 7 -const OUTFORMAT_TABLE = 8 -const OUTFORMAT_BOX = 9 -const OUTFORMAT_XML = 10 - -// The Result is either a Literal or a RowSet -type Result struct { - uncompressedChuckSizeSum uint64 - - value Value - - rows []ResultRow - ColumnNames []string - ColumnWidth []uint64 - MaxHeaderWidth uint64 -} - -var OKResult = Result{ - value: Value{Type: '+', Buffer: []byte("OK")}, // This is an unset Value - rows: nil, - ColumnNames: nil, - ColumnWidth: nil, - MaxHeaderWidth: 0, - uncompressedChuckSizeSum: 0, -} - -func (this *Result) Rows() []ResultRow { return this.rows } - -// ResultSet Methods (100% GO) - -// GetType returns the type of this query result as an integer (see: RESULT_ constants). -func (this *Result) GetType() byte { return this.value.GetType() } - -// IsOK returns true if this query result if of type "RESULT_OK", false otherwise. -func (this *Result) IsOK() bool { return this.value.IsOK() } - -// GetNumberOfRows returns the number of rows in this query result -func (this *Result) GetNumberOfRows() uint64 { - switch { - case !this.IsRowSet(): - return 0 - default: - return uint64(len(this.rows)) - } -} - -// GetNumberOfColumns returns the number of columns in this query result -func (this *Result) GetNumberOfColumns() uint64 { - switch { - case !this.IsRowSet(): - return 0 - default: - return uint64(len(this.ColumnWidth)) - } -} - -// Dump outputs this query result to the screen. -// Warning: No line truncation is used. If you want to truncation the output to a certain width, use: Result.DumpToScreen( width ) -func (this *Result) Dump() { - w := 0 - if width, _, err := term.GetSize(0); err == nil { - w = width - } - this.DumpToScreen(uint(w)) -} - -// ToJSON returns a JSON representation of this query result. -// BUG(andreas): The Result.ToJSON method is not implemented yet. -func (this *Result) ToJSON() string { - buf := new(bytes.Buffer) - _, _ = this.DumpToWriter(bufio.NewWriter(buf), OUTFORMAT_JSON, false, "", "NULL", "", 0, false) - return buf.String() -} - -// Additional ResultSet Methods (100% GO) - -// GetMaxColumnLength returns the number of runes of the value in the specified column with the maximum length in this query result. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *Result) GetMaxColumnWidth(Column uint64) (uint64, error) { - switch { - case !this.IsRowSet(): - return 0, errors.New("Not a RowSet") - case Column >= this.GetNumberOfColumns(): - return 0, errors.New("Column Index out of bounds") - default: - return this.ColumnWidth[Column], nil - } -} - -// GetNameWidth returns the number of runes of the column name in the specified column. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *Result) GetNameLength(Column uint64) (uint64, error) { - switch { - case !this.IsRowSet(): - return 0, errors.New("Not a RowSet") - case Column >= this.GetNumberOfColumns(): - return 0, errors.New("Column Index out of bounds") - default: - return uint64(len(this.ColumnNames[Column])), nil - } -} - -// GetMaxNameWidth returns the number of runes of the longest column name. -func (this *Result) GetMaxNameWidth() uint64 { return this.MaxHeaderWidth } - -// Additional Data Access Functions (100% GO) - -// IsError returns true if this query result is of type "RESULT_ERROR", false otherwise. -func (this *Result) IsError() bool { return this.value.IsError() } - -// IsNull returns true if this query result is of type "RESULT_NULL", false otherwise. -func (this *Result) IsNULL() bool { return this.value.IsNULL() } - -// IsJson returns true if this query result is of type "RESULT_JSON", false otherwise. -func (this *Result) IsJSON() bool { return this.value.IsJSON() } - -// IsString returns true if this query result is of type "RESULT_STRING", false otherwise. -func (this *Result) IsString() bool { return this.value.IsString() } - -// IsInteger returns true if this query result is of type "RESULT_INTEGER", false otherwise. -func (this *Result) IsInteger() bool { return this.value.IsInteger() } - -// IsFloat returns true if this query result is of type "RESULT_FLOAT", false otherwise. -func (this *Result) IsFloat() bool { return this.value.IsFloat() } - -// IsPSUB returns true if this query result is of type "RESULT_XXXXXXX", false otherwise. -func (this *Result) IsPSUB() bool { return this.value.IsPSUB() } - -// IsCommand returns true if this query result is of type "RESULT_XXXXXXX", false otherwise. -func (this *Result) IsCommand() bool { return this.value.IsCommand() } - -// IsReconnect returns true if this query result is of type "RESULT_XXXXXXX", false otherwise. -func (this *Result) IsReconnect() bool { return this.value.IsReconnect() } - -func (this *Result) IsBLOB() bool { return this.value.IsBLOB() } - -func (this *Result) IsArray() bool { return this.value.IsArray() } - -// IsText returns true if this query result is of type "RESULT_JSON", "RESULT_STRING", "RESULT_INTEGER" or "RESULT_FLOAT", false otherwise. -func (this *Result) IsText() bool { return this.value.IsText() } - -// IsRowSet returns true if this query result is of type "RESULT_ROWSET", false otherwise. -func (this *Result) IsRowSet() bool { - switch { - case !this.value.IsRowSet() && !this.value.IsArray(): - return false - case this.rows == nil: - return false - case this.ColumnNames == nil: - return false - case this.ColumnWidth == nil: - return false - case this.MaxHeaderWidth == 0: - return false - default: - return true - } -} -func (this *Result) IsLiteral() bool { return !this.IsRowSet() } - -// ResultSet Buffer/Scalar Methods - -// GetUncompressedChuckSizeSum returns the -func (this *Result) GetUncompressedChuckSizeSum() uint64 { return this.uncompressedChuckSizeSum } - -// GetBufferLength returns the length of the buffer of this query result. -func (this *Result) GetBufferLength() (uint64, error) { - switch { - case this.IsRowSet(): - return 0, errors.New("Not a scalar value") - default: - return this.value.GetLength(), nil - } -} - -// GetBuffer returns the buffer of this query result as string. -func (this *Result) GetBuffer() []byte { return this.value.GetBuffer() } - -func (this *Result) GetString() (string, error) { - switch { - case this.IsRowSet(): - return "", errors.New("Not a literal") - default: - return this.value.GetString(), nil - } -} -func (this *Result) GetString_() string { - value, _ := this.GetString() - return value -} - -func (this *Result) GetJSON() (object interface{}, err error) { - switch { - case !this.IsJSON(): - return nil, errors.New("Not a JSON object") - default: - err = json.Unmarshal(this.value.GetBuffer(), object) - return - } -} -func (this *Result) GetJSON_() (object interface{}) { - value, _ := this.GetJSON() - return value -} - -func (this *Result) GetInt32() (int32, error) { - switch { - case !this.IsInteger(): - return 0, errors.New("Not an integer value") - default: - return this.value.GetInt32() - } -} -func (this *Result) GetInt32_() int32 { - value, _ := this.GetInt32() - return value -} - -func (this *Result) GetInt64() (int64, error) { - switch { - case !this.IsInteger(): - return 0, errors.New("Not an integer value") - default: - return this.value.GetInt64() - } -} -func (this *Result) GetInt64_() int64 { - value, _ := this.GetInt64() - return value -} - -func (this *Result) GetFloat32() (float32, error) { - switch { - case !this.IsFloat(): - return 0, errors.New("Not a float value") - default: - return this.value.GetFloat32() - } -} -func (this *Result) GetFloat32_() float32 { - value, _ := this.GetFloat32() - return value -} - -func (this *Result) GetFloat64() (float64, error) { - switch { - case !this.IsFloat(): - return 0, errors.New("Not a float value") - default: - return this.value.GetFloat64() - } -} -func (this *Result) GetFloat64_() float64 { - value, _ := this.GetFloat64() - return value -} - -// GetError returns the ErrorCode, ExtErrorCode, ErrorOffset, ErrorMessage -// and the error object of the receiver -func (this *Result) GetError() (int, int, int, string, error) { - switch { - case !this.IsError(): - return 0, NO_EXTCODE, NO_OFFCODE, "", errors.New("Not an error") - default: - return this.value.GetError() - } -} -func (this *Result) GetError_() (int, string) { - code, _, _, message, _ := this.GetError() - return code, message -} - -func (this *Result) GetErrorAsString() string { - switch code, extcode, offset, message, err := this.GetError(); { - case err != nil: - return fmt.Sprintf("INTERNAL ERROR: %s", err.Error()) - default: - if extcode == NO_EXTCODE && offset == NO_OFFCODE { - return fmt.Sprintf("ERROR: %s (%d)", message, code) - } else { - return fmt.Sprintf("ERROR: %s (%d:%d:%d)", message, code, extcode, offset) - } - } -} - -// Free frees all memory allocated by this query result. -func (this *Result) Free() { - this.value = Value{Type: 0, Buffer: nil} // GC - this.rows = []ResultRow{} // GC - this.ColumnNames = []string{} // GC - this.ColumnWidth = []uint64{} // GC - this.MaxHeaderWidth = 0 -} - -// GetName returns the column name in column Column of this query result. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *Result) GetName(Column uint64) (string, error) { - switch { - case Column >= this.GetNumberOfColumns(): - return "", errors.New("Column Index out of bounds") - default: - return this.ColumnNames[Column], nil - } -} -func (this *Result) GetName_(Column uint64) string { - value, _ := this.GetName(Column) - return value -} - -// DumpToScreen outputs this query result to the screen. -// The output is truncated at a maximum line width of MaxLineLength runes (compare: Result.Dump()) -func (this *Result) DumpToScreen(MaxLineLength uint) { - _, _ = this.DumpToWriter(bufio.NewWriter(os.Stdout), OUTFORMAT_BOX, false, "│", "NULL", "\r\n", MaxLineLength, false) -} - -////// Row Methods (100% GO) - -// GetRow returns a pointer to the row Row of this query result. -// The Row index is an unsigned int in the range of 0...GetNumberOfRows() - 1. -// If the index row can not be found, nil is returned instead. -func (this *Result) GetRow(Row uint64) (*ResultRow, error) { - switch { - case !this.IsRowSet(): - return nil, errors.New("Not a Rowset") - case Row >= this.GetNumberOfRows(): - return nil, errors.New("Row index is out of bounds") - default: - return &this.rows[Row], nil - } -} - -// GetFirstRow returns the first row of this query result. -// If this query result has no row's, nil is returned instead. -func (this *Result) GetFirstRow() (*ResultRow, error) { return this.GetRow(0) } - -// GetLastRow returns the first row of this query result. -// If this query result has no row's, nil is returned instead. -func (this *Result) GetLastRow() (*ResultRow, error) { return this.GetRow(this.GetNumberOfRows() - 1) } - -// Additional Row Methods <- sollte es eigentlich nicht geben!!!!! - -func (this *Result) GetValue(Row uint64, Column uint64) (*Value, error) { - switch row, err := this.GetRow(Row); { - case err != nil: - return nil, err - default: - return row.GetValue(Column) - } -} - -// GetValueType returns the type of the value in row Row and column Column of this query result. -// The Row index is an unsigned int in the range of 0...GetNumberOfRows() - 1. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -// Possible return types are: VALUE_INTEGER, VALUE_FLOAT, VALUE_TEXT, VALUE_BLOB, VALUE_NULL -func (this *Result) GetValueType(Row uint64, Column uint64) (byte, error) { - switch value, err := this.GetValue(Row, Column); { - case err != nil: - return '_', err - default: - return value.GetType(), nil - } -} -func (this *Result) GetValueType_(Row uint64, Column uint64) byte { - value, _ := this.GetValueType(Row, Column) - return value -} - -// GetStringValue returns the contents in row Row and column Column of this query result as string. -// The Row index is an unsigned int in the range of 0...GetNumberOfRows() - 1. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *Result) GetStringValue(Row uint64, Column uint64) (string, error) { - switch value, err := this.GetValue(Row, Column); { - case err != nil: - return "", err - default: - return value.GetString(), nil - } -} -func (this *Result) GetStringValue_(Row uint64, Column uint64) string { - value, _ := this.GetStringValue(Row, Column) - return value -} - -// GetInt32Value returns the contents in row Row and column Column of this query result as int32. -// The Row index is an unsigned int in the range of 0...GetNumberOfRows() - 1. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *Result) GetInt32Value(Row uint64, Column uint64) (int32, error) { - switch value, err := this.GetValue(Row, Column); { - case err != nil: - return 0, err - default: - return value.GetInt32() - } -} -func (this *Result) GetInt32Value_(Row uint64, Column uint64) int32 { - value, _ := this.GetInt32Value(Row, Column) - return value -} - -// GetInt64Value returns the contents in row Row and column Column of this query result as int64. -// The Row index is an unsigned int in the range of 0...GetNumberOfRows() - 1. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *Result) GetInt64Value(Row uint64, Column uint64) (int64, error) { - switch value, err := this.GetValue(Row, Column); { - case err != nil: - return 0, err - default: - return value.GetInt64() - } -} -func (this *Result) GetInt64Value_(Row uint64, Column uint64) int64 { - value, _ := this.GetInt64Value(Row, Column) - return value -} - -// GetFloat32Value returns the contents in row Row and column Column of this query result as float32. -// The Row index is an unsigned int in the range of 0...GetNumberOfRows() - 1. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *Result) GetFloat32Value(Row uint64, Column uint64) (float32, error) { - switch value, err := this.GetValue(Row, Column); { - case err != nil: - return 0, err - default: - return value.GetFloat32() - } -} -func (this *Result) GetFloat32Value_(Row uint64, Column uint64) float32 { - value, _ := this.GetFloat32Value(Row, Column) - return value -} - -// GetFloat64Value returns the contents in row Row and column Column of this query result as float64. -// The Row index is an unsigned int in the range of 0...GetNumberOfRows() - 1. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *Result) GetFloat64Value(Row uint64, Column uint64) (float64, error) { - switch value, err := this.GetValue(Row, Column); { - case err != nil: - return 0, err - default: - return value.GetFloat64() - } -} -func (this *Result) GetFloat64Value_(Row uint64, Column uint64) float64 { - value, _ := this.GetFloat64Value(Row, Column) - return value -} - -// GetSQLDateTime parses this query result value in Row and Column as an SQL-DateTime and returns its value. -// The Row index is an unsigned int in the range of 0...GetNumberOfRows() - 1. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *Result) GetSQLDateTime(Row uint64, Column uint64) (time.Time, error) { - switch value, err := this.GetValue(Row, Column); { - case err != nil: - return time.Unix(0, 0), err - default: - return value.GetSQLDateTime() - } -} -func (this *Result) GetSQLDateTime_(Row uint64, Column uint64) time.Time { - value, _ := this.GetSQLDateTime(Row, Column) - return value -} - -//////////////////////////// - -func trimStringToMaxLength(Buffer string, MaxLineLength uint) string { - switch { - case MaxLineLength == 0: - return Buffer - case MaxLineLength >= uint(len([]rune(Buffer))): - return Buffer - default: - return fmt.Sprintf(fmt.Sprintf("%%.%ds…", MaxLineLength-1), Buffer) - } -} -func renderCenteredString(Buffer string, Width int) string { - return fmt.Sprintf("%[1]*s", -Width, fmt.Sprintf("%[1]*s", (Width+len(Buffer))/2, Buffer)) -} - -func (this *Result) renderHorizontalTableLine(Left string, Fill string, Separator string, Right string) string { - outBuffer := "" - for _, columnWidth := range this.ColumnWidth { - outBuffer = fmt.Sprintf("%s%s%s", outBuffer, strings.Repeat(Fill, int(columnWidth+2)), Separator) - } - return fmt.Sprintf("%s%s%s", Left, strings.TrimRight(outBuffer, Separator), Right) -} -func (this *Result) renderTableColumnNames(Left string, Separator string, Right string) string { - outBuffer := "" - for forThisColumn, columnWidth := range this.ColumnWidth { - columnName, _ := this.GetName(uint64(forThisColumn)) - outBuffer = fmt.Sprintf("%s%s%s", outBuffer, renderCenteredString(columnName, int(columnWidth+2)), Separator) - } - return fmt.Sprintf("%s%s%s", Left, strings.TrimRight(outBuffer, Separator), Right) -} -func (this *Result) renderTableHeader(Format int, Separator string, NewLine string, MaxLineLength uint) string { - switch Format { - case OUTFORMAT_JSON: - return fmt.Sprintf("[%s", NewLine) - - case OUTFORMAT_MARKDOWN: - return trimStringToMaxLength(this.renderTableColumnNames(Separator, Separator, Separator), MaxLineLength) + NewLine + - trimStringToMaxLength(this.renderHorizontalTableLine(Separator, "-", Separator, Separator), MaxLineLength) + NewLine - - case OUTFORMAT_TABLE: - tableLine := trimStringToMaxLength(this.renderHorizontalTableLine("+", "-", "+", "+"), MaxLineLength) + NewLine - return tableLine + - trimStringToMaxLength(this.renderTableColumnNames(Separator, Separator, Separator), MaxLineLength) + NewLine + - tableLine - - case OUTFORMAT_BOX: - return trimStringToMaxLength(this.renderHorizontalTableLine("┌", "─", "┬", "┐"), MaxLineLength) + NewLine + - trimStringToMaxLength(this.renderTableColumnNames(Separator, Separator, Separator), MaxLineLength) + NewLine + - trimStringToMaxLength(this.renderHorizontalTableLine("├", "─", "┼", "┤"), MaxLineLength) + NewLine - case OUTFORMAT_XML: - return trimStringToMaxLength("", MaxLineLength) + NewLine + - trimStringToMaxLength(fmt.Sprintf("", Separator), MaxLineLength) + NewLine - - default: - return "" // No header - } -} -func (this *Result) renderTableFooter(Format int, NewLine string, MaxLineLength uint) string { - switch Format { - case OUTFORMAT_JSON: - return trimStringToMaxLength("]", MaxLineLength) + NewLine - case OUTFORMAT_TABLE: - return trimStringToMaxLength(this.renderHorizontalTableLine("+", "-", "+", "+"), MaxLineLength) + NewLine - case OUTFORMAT_BOX: - return trimStringToMaxLength(this.renderHorizontalTableLine("└", "─", "┴", "┘"), MaxLineLength) + NewLine - case OUTFORMAT_XML: - return trimStringToMaxLength("", MaxLineLength) + NewLine - default: - return "" // No footer - } -} - -// DumpToWriter renders this query result into the buffer of an io.Writer. -// The output Format can be specified and must be one of the following values: OUTFORMAT_LIST, OUTFORMAT_CSV, OUTFORMAT_QUOTE, OUTFORMAT_TABS, OUTFORMAT_LINE, OUTFORMAT_JSON, OUTFORMAT_HTML, OUTFORMAT_MARKDOWN, OUTFORMAT_TABLE, OUTFORMAT_BOX -// The Separator argument specifies the column separating string (default: '|'). -// All lines are truncated at MaxLineLeength. A MaxLineLangth of '0' means no truncation. -// If this query result is of type RESULT_OK and SuppressOK is set to false, an "OK" string is written to the buffer, otherwise nothing is written to the buffer. -func (this *Result) DumpToWriter(Out *bufio.Writer, Format int, NoHeader bool, Separator string, NullValue string, NewLine string, MaxLineLength uint, SuppressOK bool) (int, error) { - defer Out.Flush() - - if sep, err := GetDefaultSeparatorForOutputFormat(Format); err != nil { - return 0, err - } else if strings.ToUpper(strings.TrimSpace(Separator)) == "" { - Separator = sep - } - - if strings.TrimSpace(NullValue) == "" { - NullValue = "NULL" - } - - // fmt.Printf( "Type = %d\r\n", this.Type ) - - switch { - case this.IsOK(): - if SuppressOK { - return 0, nil - } else { - return io.WriteString(Out, fmt.Sprintf("OK%s", NewLine)) - } - - case this.IsNULL(): - return io.WriteString(Out, fmt.Sprintf("%s%s", NullValue, NewLine)) - - case this.IsError(): - return io.WriteString(Out, fmt.Sprintf("%s%s", this.GetErrorAsString(), NewLine)) - - case this.IsString(), this.IsInteger(), this.IsFloat(), this.IsJSON(): - return io.WriteString(Out, string(this.GetBuffer())+NewLine) - - case this.IsArray(): - fallthrough - - case this.IsRowSet(): - var totalOutputLength int = 0 - - if !NoHeader { // Render Table Header incl. new line - if len, err := io.WriteString(Out, this.renderTableHeader(Format, Separator, NewLine, MaxLineLength)); err == nil { - totalOutputLength += len - } else { - return len + totalOutputLength, err - } - } - - // Render Table Body incl. new line - for row, err := this.GetFirstRow(); err == nil && row != nil; row = row.Next() { - if len, err := row.DumpToWriter(Out, Format, Separator, NullValue, NewLine, MaxLineLength); err == nil { - totalOutputLength += len - } else { - return len + totalOutputLength, err - } - if row.Next() != nil { - switch Format { - case OUTFORMAT_JSON: - if len, err := io.WriteString(Out, Separator); err == nil { - totalOutputLength += len - } else { - return len + totalOutputLength, err - } - } - } - } - - if !NoHeader { // Render Table Footer - if len, err := io.WriteString(Out, this.renderTableFooter(Format, NewLine, MaxLineLength)); err == nil { - totalOutputLength += len - } else { - return len + totalOutputLength, err - } - } - - if err := Out.Flush(); err != nil { - return totalOutputLength, err - } - return totalOutputLength, nil - - default: - return 0, errors.New("Unknown Output Format") - } -} - -func GetOutputFormatFromString(Format string) (int, error) { - switch strings.ToUpper(strings.TrimSpace(Format)) { - case "LIST": - return OUTFORMAT_LIST, nil - case "CSV": - return OUTFORMAT_CSV, nil - case "QUOTE": - return OUTFORMAT_QUOTE, nil - case "TABS": - return OUTFORMAT_TABS, nil - case "LINE": - return OUTFORMAT_LINE, nil - case "JSON": - return OUTFORMAT_JSON, nil - case "HTML": - return OUTFORMAT_HTML, nil - case "MARKDOWN": - return OUTFORMAT_MARKDOWN, nil - case "TABLE": - return OUTFORMAT_TABLE, nil - case "BOX": - return OUTFORMAT_BOX, nil - case "XML": - return OUTFORMAT_XML, nil - case "": - return -1, errors.New("Missing output format") - default: - return -1, errors.New("Unknown output format") - } -} - -func GetDefaultSeparatorForOutputFormat(Format int) (string, error) { - switch Format { - case OUTFORMAT_LIST, OUTFORMAT_MARKDOWN, OUTFORMAT_TABLE: - return "|", nil - case OUTFORMAT_CSV, OUTFORMAT_QUOTE, OUTFORMAT_JSON: - return ",", nil - case OUTFORMAT_TABS: - return "\t", nil - case OUTFORMAT_LINE: - return "=", nil - case OUTFORMAT_HTML, OUTFORMAT_XML: - return "", nil - case OUTFORMAT_BOX: - return "│", nil - default: - return "", errors.New("Unknown output format") - } -} - -////// -// is called from connection.Select -func (this *SQCloud) readResult() (*Result, error) { - ErrorResult := Result{ - value: Value{Type: '-', Buffer: []byte("100000 Unknown internal error")}, // This is an unset Value - rows: nil, - ColumnNames: nil, - ColumnWidth: nil, - MaxHeaderWidth: 0, - uncompressedChuckSizeSum: 0, - } - - result := ErrorResult - - var rowIndex uint64 = 0 - - for { // loop through all chunks - - if chunk, err := this.readNextRawChunk(); err != nil { - ErrorResult.uncompressedChuckSizeSum = chunk.LEN - ErrorResult.value.Buffer = []byte(fmt.Sprintf("100001 Internal Error: SQCloud.readNextRawChunk (%s)", err.Error())) - return &ErrorResult, err - - } else { - - if err := chunk.Uncompress(); err != nil { - ErrorResult.uncompressedChuckSizeSum = chunk.LEN - ErrorResult.value.Buffer = []byte(fmt.Sprintf("100002 Internal Error: Chunk.Uncompress (%s)", err.Error())) - return &ErrorResult, err - - } else { - result.uncompressedChuckSizeSum += chunk.LEN - - switch Type := chunk.GetType(); Type { - case CMD_COMPRESSED: - return nil, errors.New("Nested compression") - - // Values - case CMD_NULL: - fallthrough // NULL - case CMD_INT, CMD_FLOAT: - fallthrough // INT, FLOAT - case CMD_STRING, CMD_ZEROSTRING, CMD_BLOB, CMD_ERROR, CMD_JSON: - fallthrough // String, C-String, BLOB, Error-String, JSON-String - case CMD_PUBSUB: - fallthrough // PSUB - case CMD_COMMAND: - fallthrough // Command - case CMD_RECONNECT: // Reconnect - result.value.Type = Type - switch bytesRead, err := result.value.readBufferAt(chunk, 1); { - case err != nil: - return nil, err - case bytesRead == 0: - return nil, errors.New("No Data") - case Type == CMD_PUBSUB: - // PSUB - pauth := result.GetString_() - - if this.psub == nil { - tokens := append(strings.SplitN(pauth, " ", 3), []string{"", "", ""}...) // make sure that there are enough elements for max index 2 - - // this.psubc = make(chan string) - this.psub = &SQCloud{ - SQCloudConfig: SQCloudConfig{ - Host: this.Host, - Port: this.Port, - Username: "", - Password: "", - Database: "", - Timeout: 0, - }, - - sock: nil, - psub: nil, - cert: this.cert, - - uuid: tokens[1], - secret: tokens[2], - - ErrorCode: 0, - ErrorMessage: "", - } - - if err := this.psub.reconnect(); err != nil { - _ = this.psub.Close() - this.psub = nil - return nil, err - } - - if _, err := this.psub.sendString(pauth); err != nil { - _ = this.psub.Close() - this.psub = nil - return nil, err - } - if result2, err2 := this.psub.readResult(); result2 != nil { - defer result2.Free() - - if err2 != nil { - _ = this.psub.Close() - this.psub = nil - return nil, err2 - } - - if !result2.IsString() || result2.GetString_() != "OK" { - _ = this.psub.Close() - this.psub = nil - return nil, errors.New("unexpected result") - } - - go func() { - for { - result, err := this.psub.readResult() - if result != nil { - defer result.Free() - } - if err != nil { - return - } - if result == nil { - return - } - if !result.IsJSON() { - return - } - - this.Callback(this, result.GetString_()) - // select { - // case this.psubc <- result.GetString_(): - // default: - // // no message sent - // } - } - }() - } - } - result = OKResult - result.uncompressedChuckSizeSum = chunk.LEN - - fallthrough - default: - return &result, nil - } - - // Array - case CMD_ARRAY: - var offset uint64 = 1 // skip the first type byte - var N uint64 = 0 - var bytesRead uint64 = 0 - - if _, _, bytesRead, err = chunk.readUInt64At(offset); err != nil { - return nil, err - } - offset += bytesRead - - if N, _, bytesRead, err = chunk.readUInt64At(offset); err != nil { - return nil, err - } // 0..N-values - offset += bytesRead - - result.rows = make([]ResultRow, int(N)) - result.ColumnNames = []string{"ARRAY"} - result.ColumnWidth = []uint64{5} - result.MaxHeaderWidth = 0 - - for row := uint64(0); row < N; row++ { - result.rows[row].result = &result - result.rows[row].index = row - result.rows[row].columns = make([]Value, 1) - - switch result.rows[row].columns[0], bytesRead, err = chunk.readValueAt(offset); { - case err != nil: - return nil, err - default: - columnLength := result.rows[row].columns[0].GetLength() - if result.ColumnWidth[0] < columnLength { - result.ColumnWidth[0] = columnLength - } - if result.MaxHeaderWidth < columnLength { - result.MaxHeaderWidth = columnLength - } - offset += bytesRead - } - } - - result.value.Type = '=' - result.value.Buffer = nil - - return &result, nil - - // RowSet - case CMD_ROWSET_CHUNK, CMD_ROWSET: - // RowSet: *LEN 0:VERSION ROWS COLS DATA - // RowSet Chunk: /LEN IDX:VERSION ROWS COLS DATA - - var offset uint64 = 1 // skip the first type byte - var bytesRead uint64 = 0 - var LEN uint64 = 0 - var IDX uint64 = 0 - var VERSION uint64 = 0 - var NROWS uint64 = 0 - var NCOLS uint64 = 0 - - // Detect end of Rowset Chunk directly without parsing... - if Type == CMD_ROWSET_CHUNK { - for _, pattern := range rowsetChunkEndPatterns { - if chunk.RAW[offset] == pattern[0] && bytes.Equal(chunk.RAW[offset:offset+uint64(len(pattern))], pattern) { - return &result, nil - } - } - } - - if LEN, _, bytesRead, err = chunk.readUInt64At(offset); err != nil { - return nil, err - } - offset += bytesRead - - if IDX, VERSION, bytesRead, err = chunk.readUInt64At(offset); err != nil { - return nil, err - } - offset += bytesRead - - if NROWS, _, bytesRead, err = chunk.readUInt64At(offset); err != nil { - return nil, err - } // 0..rows-1 - offset += bytesRead - - if NCOLS, _, bytesRead, err = chunk.readUInt64At(offset); err != nil { - return nil, err - } // 0..columns-1 - offset += bytesRead - - LEN = LEN + offset // check for overreading... - - if Type == CMD_ROWSET_CHUNK && NROWS == 0 && NCOLS == 0 { - return &result, nil - } - - // single chunk or first chunk of multiple chunks - if IDX == 0 || IDX == 1 { - result.rows = []ResultRow{} - result.ColumnNames = make([]string, int(NCOLS)) - result.ColumnWidth = make([]uint64, int(NCOLS)) - result.MaxHeaderWidth = 0 - - for column := uint64(0); column < NCOLS; column++ { // Read in the column names, use the result.value as scratch variable - switch val, bytesRead, err := chunk.readValueAt(offset); { - case err != nil: - return nil, err - case !val.IsString(): - return nil, errors.New("Invalid Column name") - default: - result.ColumnNames[column] = val.GetString() - result.ColumnWidth[column] = val.GetLength() - if result.MaxHeaderWidth < result.ColumnWidth[column] { - result.MaxHeaderWidth = result.ColumnWidth[column] - } - offset += bytesRead - } - } - } - - if VERSION != 1 { - // not yet supported - return nil, fmt.Errorf("Unsupported rowset version %d", VERSION) - } - - // read all the rows from this chunk - rows := make([]ResultRow, int(NROWS)) - for row := uint64(0); row < NROWS; row++ { - - rows[row].result = &result - rows[row].index = rowIndex - rows[row].columns = make([]Value, int(NCOLS)) - - rowIndex++ - - for column := uint64(0); column < NCOLS; column++ { - switch rows[row].columns[column], bytesRead, err = chunk.readValueAt(offset); { - case err != nil: - return nil, err - default: - columnLength := rows[row].columns[column].GetLength() - if result.ColumnWidth[column] < columnLength { - result.ColumnWidth[column] = columnLength - } - if result.MaxHeaderWidth < columnLength { - result.MaxHeaderWidth = columnLength - } - offset += bytesRead - } - } - } - - result.rows = append(result.rows, rows...) - - result.value.Type = CMD_ROWSET - result.value.Buffer = nil - - if Type == CMD_ROWSET { - return &result, nil - } // return if it is a rowset - if _, err := this.sendString("OK"); err != nil { // ask the server for the next chunk and loop (Thank's Andrea) - return nil, err - } - - case CMD_RAWJSON: - result.value.Type = CMD_JSON // translate JSON Type to uniform '#' - result.value.Buffer = chunk.GetData() - return &result, nil - - default: - return nil, errors.New("Unknown response type") - } - } - } - } -} diff --git a/GO/sdk/row.go b/GO/sdk/row.go deleted file mode 100644 index 840272a4..00000000 --- a/GO/sdk/row.go +++ /dev/null @@ -1,363 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud GO SDK -// /// /// /// Version : 1.0.0 -// // /// /// /// Date : 2021/08/31 -// /// /// /// /// Author : Andreas Pfeil -// /// /// /// /// -// /// ////////// /// /// Description : GO Methods related to the -// //// /// /// ResultRow class. -// //// ////////// /// -// //// //// -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -package sqlitecloud - -import ( - "encoding/json" - "errors" - "fmt" - "html" - "io" - "strings" - "time" -) - -type ResultRow struct { - result *Result - index uint64 `json:"Index"` // 0, 1, ... rows-1 - columns []Value `json:"ColumnValues"` -} - -// ToJSON returns a JSON representation of this query result row. -// BUG(andreas): The ResultRow.ToJSON method is not implemented yet. -func (this *ResultRow) ToJSON() ([]byte, error) { return json.Marshal(this) } - -// IsFirst returns true if this query result row is the first in the result set, false otherwise. -func (this *ResultRow) IsFirst() bool { - switch { - case this.result.GetNumberOfRows() < 1: - return false - default: - return this.index == 0 - } -} - -// IsLast returns true if this query result row is the last in the result set, false otherwise. -func (this *ResultRow) IsLast() bool { - switch { - case this.result.GetNumberOfRows() < 1: - return false - default: - return this.index == this.result.GetNumberOfRows()-1 - } -} - -// IsEOF returns false if this query result row is in the result set, true otherwise. -func (this *ResultRow) IsEOF() bool { - switch { - case this.result.GetNumberOfRows() < 1: - return true - default: - return this.index >= this.result.GetNumberOfRows() - } -} - -// Rewind resets the iterator and returns the first row in this query result. -func (this *ResultRow) Rewind() *ResultRow { - switch row, err := this.result.GetFirstRow(); { - case err != nil: - return nil - default: - return row - } -} - -// Next fetches the next row in this query result and returns it, otherwise if there is no next row, nil is returned. -func (this *ResultRow) Next() *ResultRow { - switch row, err := this.result.GetRow(this.index + 1); { - case err != nil: - return nil - default: - return row - } -} - -func (this *ResultRow) GetNumberOfColumns() uint64 { return uint64(len(this.columns)) } - -func (this *ResultRow) GetValue(Column uint64) (*Value, error) { - switch { - case Column >= this.GetNumberOfColumns(): - return nil, errors.New("Column index out of bounds") - default: - return &this.columns[Column], nil - } -} - -// GetMaxNameLength returns the number of runes of the longest column name. -func (this *ResultRow) GetMaxNameLength() uint64 { return this.result.GetMaxNameWidth() } - -// GetNameLength returns the number of runes of the column name in the specified column. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *ResultRow) GetNameLength(Column uint64) (uint64, error) { - return this.result.GetNameLength(Column) -} - -// GetName returns the column name in column Column of this query result. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *ResultRow) GetName(Column uint64) (string, error) { return this.result.GetName(Column) } - -// GetMaxWidth returns the number of runes of the value in the specified column with the maximum length in this query result. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -// BUG(andreas): Rename GetWidth->GetmaxWidth -func (this *ResultRow) GetMaxWidth(Column uint64) (uint64, error) { - return this.result.GetMaxColumnWidth(Column) -} - -// Die folgenden Methoden sollten überflüssig sein... - -// GetType returns the type of the value in column Column of this query result row. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -// Possible return types are: VALUE_INTEGER, VALUE_FLOAT, VALUE_TEXT, VALUE_BLOB, VALUE_NULL -func (this *ResultRow) GetType(Column uint64) (byte, error) { - switch value, err := this.GetValue(Column); { - case err != nil: - return CMD_NULL, err - case value == nil: - return CMD_NULL, errors.New("Column index out of bounds") - default: - return value.GetType(), nil - } -} - -// IsInteger returns true if this query result row column Column is of type "VALUE_INTEGER", false otherwise. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *ResultRow) IsInteger(Column uint64) bool { - switch value, err := this.GetValue(Column); { - case err != nil: - return false - case value == nil: - return false - default: - return value.IsInteger() - } -} - -// IsFloat returns true if this query result row column Column is of type "VALUE_FLOAT", false otherwise. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *ResultRow) IsFloat(Column uint64) bool { - switch value, err := this.GetValue(Column); { - case err != nil: - return false - case value == nil: - return false - default: - return value.IsFloat() - } -} - -// IsString returns true if this query result row column Column is of type "VALUE_TEXT", false otherwise. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *ResultRow) IsString(Column uint64) bool { - switch value, err := this.GetValue(Column); { - case err != nil: - return false - case value == nil: - return false - default: - return value.IsString() - } -} - -// IsBLOB returns true if this query result row column Column is of type "VALUE_BLOB", false otherwise. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *ResultRow) IsBLOB(Column uint64) bool { - switch value, err := this.GetValue(Column); { - case err != nil: - return false - case value == nil: - return false - default: - return value.IsBLOB() - } -} - -// IsNULL returns true if this query result row column Column is of type "VALUE_NULL", false otherwise. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *ResultRow) IsNULL(Column uint64) bool { - switch value, err := this.GetValue(Column); { - case err != nil: - return false - case value == nil: - return false - default: - return value.IsNULL() - } -} - -// IsText returns true if this query result row column Column is of type "VALUE_TEXT" or "VALUE_BLOB", false otherwise. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *ResultRow) IsText(Column uint64) bool { - switch value, err := this.GetValue(Column); { - case err != nil: - return false - case value == nil: - return false - default: - return value.IsText() - } -} - -// GetStringValue returns the contents in column Column of this query result row as string. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *ResultRow) GetString(Column uint64) (string, error) { - return this.result.GetStringValue(this.index, Column) -} - -// GetInt32Value returns the contents in column Column of this query result row as int32. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *ResultRow) GetInt32(Column uint64) (int32, error) { - return this.result.GetInt32Value(this.index, Column) -} - -// GetInt64Value returns the contents in column Column of this query result row as int64. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *ResultRow) GetInt64(Column uint64) (int64, error) { - return this.result.GetInt64Value(this.index, Column) -} - -// GetFloat32Value returns the contents in column Column of this query result row as float32. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *ResultRow) GetFloat32(Column uint64) (float32, error) { - return this.result.GetFloat32Value(this.index, Column) -} - -// GetFloat64Value returns the contents in column Column of this query result row as float64. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *ResultRow) GetFloat64(Column uint64) (float64, error) { - return this.result.GetFloat64Value(this.index, Column) -} - -// GetSQLDateTime parses this query result value in column Column as an SQL-DateTime and returns its value. -// The Column index is an unsigned int in the range of 0...GetNumberOfColumns() - 1. -func (this *ResultRow) GetSQLDateTime(Column uint64) (time.Time, error) { - return this.result.GetSQLDateTime(this.index, Column) -} - -//////// - -func (this *ResultRow) renderValue(Column uint64, Quotation string, NullValue string) string { - //fmt.Printf( "renderValue, col = %d\r\n", Column ) - - if val, err := this.GetValue(Column); err != nil { - return "" - } else { - switch { - case val.IsNULL(): - return NullValue - case val.IsText(): - return val.GetString() - default: - return fmt.Sprintf("%s%s%s", Quotation, val.GetString(), Quotation) - } - } -} - -func (this *ResultRow) renderLine(Format int, Separator string, NullValue string, NewLine string, MaxLineLength uint) string { - buffer := "" - for forThisColumn := uint64(0); forThisColumn < this.result.GetNumberOfColumns(); forThisColumn++ { - maxWidth, _ := this.GetMaxWidth(forThisColumn) - columnName, _ := this.GetName(forThisColumn) - - switch Format { - case OUTFORMAT_LIST, OUTFORMAT_TABS: - buffer += fmt.Sprintf("%s%s", this.renderValue(forThisColumn, "", NullValue), Separator) - case OUTFORMAT_MARKDOWN, OUTFORMAT_TABLE, OUTFORMAT_BOX: - buffer += fmt.Sprintf(fmt.Sprintf(" %%-%ds %s", maxWidth, Separator), this.renderValue(forThisColumn, "", NullValue)) - case OUTFORMAT_CSV: - buffer += fmt.Sprintf("%s,", SQCloudEnquoteString(this.renderValue(forThisColumn, "", NullValue))) - case OUTFORMAT_LINE: - buffer += trimStringToMaxLength(fmt.Sprintf(fmt.Sprintf("%%%ds = %%s", this.result.MaxHeaderWidth), columnName, this.renderValue(forThisColumn, "", NullValue)), MaxLineLength) + NewLine - case OUTFORMAT_HTML: - buffer += trimStringToMaxLength(fmt.Sprintf(" %s", html.EscapeString(this.renderValue(forThisColumn, "", NullValue))), MaxLineLength) + NewLine - case OUTFORMAT_XML: - buffer += trimStringToMaxLength(fmt.Sprintf(" %s", columnName, html.EscapeString(this.renderValue(forThisColumn, "", NullValue))), MaxLineLength) + NewLine - } - } - switch Format { - case OUTFORMAT_LINE, OUTFORMAT_HTML, OUTFORMAT_XML: - return buffer // Multiline output was truncated already - default: - return trimStringToMaxLength(strings.TrimRight(buffer, Separator), MaxLineLength) - } -} - -// DumpToWriter renders this query result row into the buffer of an io.Writer. -// The output Format can be specified and must be one of the following values: OUTFORMAT_LIST, OUTFORMAT_CSV, OUTFORMAT_QUOTE, OUTFORMAT_TABS, OUTFORMAT_LINE, OUTFORMAT_JSON, OUTFORMAT_HTML, OUTFORMAT_MARKDOWN, OUTFORMAT_TABLE, OUTFORMAT_BOX -// The Separator argument specifies the column separating string (default: '|'). -// All lines are truncated at MaxLineLeength. A MaxLineLangth of '0' means no truncation. -func (this *ResultRow) DumpToWriter(Out io.Writer, Format int, Separator string, NullValue string, NewLine string, MaxLineLength uint) (int, error) { - buffer := "" - - switch Format { - case OUTFORMAT_LIST, OUTFORMAT_CSV, OUTFORMAT_TABS, OUTFORMAT_LINE: - buffer = this.renderLine(Format, Separator, NullValue, NewLine, MaxLineLength) + NewLine - - case OUTFORMAT_QUOTE: - for forThisColumn := uint64(0); forThisColumn < this.GetNumberOfColumns(); forThisColumn++ { - val, _ := this.GetString(forThisColumn) - switch Type, err := this.GetType(forThisColumn); { - case err != nil: - case Type == CMD_STRING: - fallthrough - case Type == CMD_ZEROSTRING: - fallthrough - case Type == CMD_BLOB: - buffer += fmt.Sprintf("'%s'%s", strings.Replace(val, "'", "\\'", -1), Separator) - default: - buffer += fmt.Sprintf("%s%s", val, Separator) - } - } - buffer = trimStringToMaxLength(strings.TrimRight(buffer, Separator), MaxLineLength) + NewLine - - case OUTFORMAT_JSON: - sep := Separator - for forThisColumn := uint64(0); forThisColumn < this.GetNumberOfColumns(); forThisColumn++ { - if forThisColumn == this.GetNumberOfColumns()-1 { - sep = "" - } - columnName, _ := this.GetName(forThisColumn) - switch Type, err := this.GetType(forThisColumn); { - case err != nil: - case Type == CMD_STRING: - fallthrough - case Type == CMD_ZEROSTRING: - fallthrough - case Type == CMD_BLOB: - buffer += fmt.Sprintf("\"%s\":\"%s\"%s", strings.Replace(columnName, "\"", "\\\"", -1), strings.Replace(this.renderValue(forThisColumn, "", NullValue), "\"", "\\\"", -1), sep) - default: - buffer += fmt.Sprintf("\"%s\":%s%s", strings.Replace(columnName, "\"", "\\\"", -1), this.renderValue(forThisColumn, "", NullValue), sep) - } - } - buffer = trimStringToMaxLength(fmt.Sprintf(" {%s}", buffer), MaxLineLength) + NewLine - - case OUTFORMAT_HTML: - buffer = trimStringToMaxLength("", MaxLineLength) + NewLine + - this.renderLine(Format, Separator, NullValue, NewLine, MaxLineLength) + - trimStringToMaxLength("", MaxLineLength) + NewLine - - case OUTFORMAT_XML: - buffer = trimStringToMaxLength(" ", MaxLineLength) + NewLine + - this.renderLine(Format, Separator, NullValue, NewLine, MaxLineLength) + - trimStringToMaxLength(" ", MaxLineLength) + NewLine - - case OUTFORMAT_MARKDOWN, OUTFORMAT_TABLE, OUTFORMAT_BOX: - buffer = trimStringToMaxLength(fmt.Sprintf("%s%s%s", Separator, this.renderLine(Format, Separator, NullValue, NewLine, MaxLineLength), Separator), MaxLineLength) + NewLine - } - - return io.WriteString(Out, buffer) -} diff --git a/GO/sdk/server.go b/GO/sdk/server.go deleted file mode 100644 index 60e06151..00000000 --- a/GO/sdk/server.go +++ /dev/null @@ -1,539 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud GO SDK -// /// /// /// Version : 1.0.1 -// // /// /// /// Date : 2021/08/31 -// /// /// /// /// Author : Andreas Pfeil -// /// /// /// /// -// /// ////////// /// /// Description : Go Methods related to the -// //// /// /// SQCloud class for using -// //// ////////// /// the internal server -// //// //// commands. -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -package sqlitecloud - -import ( - "errors" - "fmt" - "strconv" - "strings" - "time" -) - -type SQCloudConnection struct { - ClientID int64 - Address string - Username string - Database string - ConnectionDate time.Time - LastActivity time.Time -} - -type SQCloudNodeStatus int64 - -const ( - Leader SQCloudNodeStatus = iota - Follower - Candidate - Learner -) - -type SQCloudNodeProgress int64 - -const ( - Probe SQCloudNodeProgress = iota - Replicate - Snapshot - Unknown -) - -type SQCloudNode struct { - NodeID int64 - NodeInterface string - ClusterInterface string - Status SQCloudNodeStatus - Progress SQCloudNodeProgress - Match int64 - LastActivity time.Time -} - -type SQCloudInfo struct { - SQLiteVersion string - SQCloudVersion string - SQCloudBuildDate time.Time - SQCloudGitHash string - - ServerTime time.Time - ServerCPUs int - ServerOS string - ServerArchitecture string - - ServicePID int - ServiceStart time.Time - ServicePort int - ServiceNocluster int - ServiceNodeID int - SericeMultiplexAPI string - - TLS string - TLSConnVersion string - TLSConnCipher string - TLSConnCipherStrength int - TLSConnAlpnSelected string - TLSConnServername string - TLSPeerCertProvided int - TLSPeerCertSubject string - TLSPeerCertIssuer string - TLSPeerCertHash string - TLSPeerCertNotBefore time.Time - TLSPeerCertNotAfter time.Time -} - -type SQCloudPlugin struct { - Name string - Type string - Enabled bool - Version string - Copyright string - Description string -} - -// Node Functions - -// AddNode - INTERNAL SERVER COMMAND: Adds a node to the SQLite Cloud Database Cluster. -func (this *SQCloud) AddNode(Node string, Address string, Cluster string, Snapshot string, Learner bool) error { - sql := "ADD" - if Learner { - sql += " LEARNER" - } - sql += " NODE ? ADDRESS ? CLUSTER ? SNAPSHOT ?" - return this.ExecuteArray(sql, []interface{}{Node, Address, Cluster, Snapshot}) -} - -// RemoveNode - INTERNAL SERVER COMMAND: Removes a node to the SQLite Cloud Database Cluster. -func (this *SQCloud) RemoveNode(Node string) error { - return this.ExecuteArray("REMOVE NODE ?", []interface{}{Node}) -} - -func stringToSQCloudNodeStatus(s string) (SQCloudNodeStatus, error) { - switch strings.ToLower(s) { - case "leader": - return Leader, nil - case "follower": - return Follower, nil - case "candidate": - return Candidate, nil - case "learner": - return Learner, nil - default: - return -1, fmt.Errorf("Cannot convert '%s' to SQCloudNodeStatus", s) - } -} - -func stringToSQCloudNodeProgress(s string) (SQCloudNodeProgress, error) { - switch strings.ToLower(s) { - case "probe": - return Probe, nil - case "replicate": - return Replicate, nil - case "candidate": - return Snapshot, nil - case "unknown": - return Unknown, nil - default: - return -1, fmt.Errorf("Cannot convert '%s' to SQCloudNodeProgress", s) - } -} - -// RemoveNode - INTERNAL SERVER COMMAND: Lists all nodes of this SQLite Cloud Database Cluster. -func (this *SQCloud) ListNodes() ([]SQCloudNode, error) { - list := []SQCloudNode{} - result, err := this.Select("LIST NODES") - if err == nil { - if result != nil { - defer result.Free() - if result.GetNumberOfColumns() == 7 { - for row, rows := uint64(0), result.GetNumberOfRows(); row < rows; row++ { - node := SQCloudNode{} - node.NodeID, _ = result.GetInt64Value(row, 0) - node.NodeInterface, _ = result.GetStringValue(row, 1) - node.ClusterInterface, _ = result.GetStringValue(row, 2) - node.Status, _ = stringToSQCloudNodeStatus(result.GetStringValue_(row, 3)) - node.Progress, _ = stringToSQCloudNodeProgress(result.GetStringValue_(row, 4)) - node.Match, _ = result.GetInt64Value(row, 5) - node.LastActivity, _ = result.GetSQLDateTime(row, 6) - list = append(list, node) - } - return list, nil - } - return []SQCloudNode{}, errors.New("ERROR: Query returned not 7 Columns (-1)") - } - return []SQCloudNode{}, nil - } - return []SQCloudNode{}, err -} - -// Connection Functions - -// CloseConnection - INTERNAL SERVER COMMAND: Closes the specified connection. -func (this *SQCloud) CloseConnection(ConnectionID string) error { - return this.ExecuteArray("CLOSE CONNECTION ?", []interface{}{ConnectionID}) -} - -// ListConnections - INTERNAL SERVER COMMAND: Lists all connections of this SQLite Cloud Database Cluster. -func (this *SQCloud) ListConnections() ([]SQCloudConnection, error) { - connectionList := []SQCloudConnection{} - result, err := this.Select("LIST CONNECTIONS") - if err == nil { - if result != nil { - defer result.Free() - if result.GetNumberOfColumns() == 6 { - for row, rows := uint64(0), result.GetNumberOfRows(); row < rows; row++ { - connection := SQCloudConnection{} - connection.ClientID, _ = result.GetInt64Value(row, 0) - connection.Address, _ = result.GetStringValue(row, 1) - connection.Username, _ = result.GetStringValue(row, 2) - connection.Database, _ = result.GetStringValue(row, 3) - connection.ConnectionDate, _ = result.GetSQLDateTime(row, 4) - connection.LastActivity, _ = result.GetSQLDateTime(row, 5) - connectionList = append(connectionList, connection) - } - return connectionList, nil - } - return []SQCloudConnection{}, errors.New("ERROR: Query returned not 6 Columns (-1)") - } - return []SQCloudConnection{}, nil - } - return []SQCloudConnection{}, err -} - -func resultToConnectionList(result *Result, err error) ([]SQCloudConnection, error) { - connectionList := []SQCloudConnection{} - if err == nil { - if result != nil { - if result.GetNumberOfColumns() == 6 { - for row, rows := uint64(0), result.GetNumberOfRows(); row < rows; row++ { - connection := SQCloudConnection{} - connection.ClientID = result.GetInt64Value_(row, 0) - connection.Address = result.GetStringValue_(row, 1) - connection.Username = result.GetStringValue_(row, 2) - connection.Database = result.GetStringValue_(row, 3) - connection.ConnectionDate = result.GetSQLDateTime_(row, 4) - connection.LastActivity = result.GetSQLDateTime_(row, 5) - connectionList = append(connectionList, connection) - } - result.Free() - return connectionList, nil - } - result.Free() - return []SQCloudConnection{}, errors.New("ERROR: Query returned not 6 Columns (-1)") - } - return []SQCloudConnection{}, nil - } - return []SQCloudConnection{}, err -} - -// ListDatabaseConnections - INTERNAL SERVER COMMAND: Lists all connections that use the specified Database on this SQLite Cloud Database Cluster. -func (this *SQCloud) ListDatabaseConnections(Database string) ([]SQCloudConnection, error) { - result, err := this.SelectArray("LIST DATABASE CONNECTIONS ?", []interface{}{Database}) - return resultToConnectionList(result, err) -} - -// Auth Functions - -func authCommand(Username string, Password string) (string, []interface{}) { - return "AUTH USER ? PASSWORD ?;", []interface{}{Username, Password} -} - -// Auth - INTERNAL SERVER COMMAND: Authenticates User with the given credentials. -func (this *SQCloud) Auth(Username string, Password string) error { - return this.ExecuteArray(authCommand(Username, Password)) -} - -func authWithKeyCommand(Key string) (string, []interface{}) { - return "AUTH APIKEY ?;", []interface{}{Key} -} - -// Auth - INTERNAL SERVER COMMAND: Authenticates User with the given API KEY. -func (this *SQCloud) AuthWithKey(Key string) error { - return this.ExecuteArray(authWithKeyCommand(Key)) -} - -// Database funcitons - -// CreateDatabase - INTERNAL SERVER COMMAND: Creates a new Database on this SQLite Cloud Database Cluster. -// If the Database already exists on this Database Server, an error is returned except the NoError flag is set. -// Encoding specifies the character set Encoding that should be used for the new Database - for example "UFT-8". -func (this *SQCloud) CreateDatabase(Database string, Key string, Encoding string, NoError bool) error { - sql := "CREATE DATABASE ?" - args := []interface{}{Database} - if strings.TrimSpace(Key) != "" { - sql += " KEY ?" - args = append(args, Key) - } - if strings.TrimSpace(Encoding) != "" { - sql += " ENCODING ?" - args = append(args, Encoding) - } - if NoError { - sql += " IF NOT EXISTS" - } - // println( sql ) - return this.ExecuteArray(sql, args) -} - -// RemoveDatabase - INTERNAL SERVER COMMAND: Deletes the specified Database on this SQLite Cloud Database Cluster. -// If the given Database is not present on this Database Server or the user has not the necessary access rights, -// an error describing the problem will be returned. -// If the NoError flag is set, no error will be reported if the database does not exist. -func (this *SQCloud) RemoveDatabase(Database string, NoError bool) error { - sql := "REMOVE DATABASE ?" - if NoError { - sql += " IF EXISTS" - } - return this.ExecuteArray(sql, []interface{}{Database}) -} - -// ListDatabases - INTERNAL SERVER COMMAND: Lists all Databases that are present on this SQLite Cloud Database Cluster and returns the Names of the databases in an array of strings. -func (this *SQCloud) ListDatabases() ([]string, error) { - return this.SelectStringList("LIST DATABASES") -} - -// GetDatabase - INTERNAL SERVER COMMAND: Gets the name of the previously selected Database as string. (see: *SQCloud.UseDatabase()) -// If no database was selected, an error describing the problem is returned. -func (this *SQCloud) GetDatabase() (string, error) { - result, err := this.Select("GET DATABASE") - if result != nil { - defer result.Free() - if err != nil { - return "", err - } - return result.GetString() - } - return "", err -} - -func useDatabaseCommand(Database string) (string, []interface{}) { - return "USE DATABASE ?;", []interface{}{Database} -} - -// UseDatabase - INTERNAL SERVER COMMAND: Selects the specified Database for usage. -// Only if a database was selected, SQL Commands can be sent to this specific Database. -// An error is returned if the specified Database was not found or the user has not the necessary access rights to work with this Database. -func (this *SQCloud) UseDatabase(Database string) error { - this.Database = Database - return this.ExecuteArray(useDatabaseCommand(Database)) -} - -// UseDatabase - INTERNAL SERVER COMMAND: Releases the actual Database. -// Any further SQL commands will result in an error before selecting a new Database. (see: *SQCloud.UseDatabase()) -func (this *SQCloud) UnuseDatabase() error { - this.Database = "" - return this.Execute("UNUSE DATABASE") -} - -// Plugin Functions - -// EnablePlugin enables the SQLite Plugin on the SQlite Cloud Database server. -func (this *SQCloud) EnablePlugin(Plugin string) error { - return this.ExecuteArray("ENABLED PLUGIN ?", []interface{}{Plugin}) -} - -// DisablePlugin disables the SQLite Plugin on the SQlite Cloud Database server. -func (this *SQCloud) DisablePlugin(Plugin string) error { - return this.ExecuteArray("DISABLE PLUGIN ?", []interface{}{Plugin}) -} - -// ListPlugins list all available Plugins at the SQlite Cloud Database server and returns an array of SQCloudPlugin. -func (this *SQCloud) ListPlugins() ([]SQCloudPlugin, error) { - pluginList := []SQCloudPlugin{} - result, err := this.Select("LIST PLUGINS") - if err == nil { - if result != nil { - for row, rows := uint64(1), result.GetNumberOfRows(); row < rows; row++ { - if result.GetNumberOfColumns() == 6 { - plugin := SQCloudPlugin{} - plugin.Name, _ = result.GetStringValue(row, 0) - plugin.Type, _ = result.GetStringValue(row, 1) - plugin.Enabled = result.GetInt32Value_(row, 2) != 0 - plugin.Version, _ = result.GetStringValue(row, 3) - plugin.Copyright, _ = result.GetStringValue(row, 4) - plugin.Description, _ = result.GetStringValue(row, 5) - pluginList = append(pluginList, plugin) - - } else { - result.Free() - return []SQCloudPlugin{}, errors.New("ERROR: Query returned not 5 Columns (-1)") - } - } - result.Free() - return pluginList, nil - } - return []SQCloudPlugin{}, nil - } - return []SQCloudPlugin{}, err -} - -// Key / Value Pair functions - -// SetKey set the provided key value pair with the key Key to the string value Value. -func (this *SQCloud) SetKey(Key string, Value string) error { - return this.ExecuteArray("SET KEY ? TO ?", []interface{}{Key, Value}) -} - -// GetKey gets the Value of the key Key and returns it as a string value. -// If the Key was not found an error is returned. -// BUG(andreas): If key is not set, DB returns NULL -> does not work with current implementation -func (this *SQCloud) GetKey(Key string) (string, error) { - result, err := this.SelectArray("GET KEY ?", []interface{}{Key}) - if result != nil { - defer result.Free() - if err != nil { - return "", err - } - return result.GetString() - } - return "", err -} - -// RemoveKey deletes the key value pair referenced with Key. -// If the Key does not exists, no error is returned. -func (this *SQCloud) RemoveKey(Key string) error { - return this.ExecuteArray("REMOVE KEY ?", []interface{}{Key}) -} - -// ListKeys lists all key value pairs on the server and returns an array of SQCloudKeyValues. -func (this *SQCloud) ListKeys() (map[string]string, error) { - return this.SelectKeyValues("LIST KEYS") -} - -// ListClientKeys lists all client/connection specific keys and values and returns the data in an array of type SQCloudKeyValues. -func (this *SQCloud) ListClientKeys() (map[string]string, error) { - return this.SelectKeyValues("LIST CLIENT KEYS") -} - -// ListDatabaseKeys lists all server specific keys and values and returns an array of type SQCloudKeyValues. -func (this *SQCloud) ListDatabaseKeys(Database string) (map[string]string, error) { - return this.SelectArrayKeyValues("LIST DATABASE ? KEYS", []interface{}{Database}) -} - -/// Misc functions - -// Ping sends the PING command to the SQLite Cloud Database Server and returns nil if it got a PONG answer. -// If no PONG was received or a timeout occurred, an error describing the problem is retuned. -func (this *SQCloud) Ping() error { - if result, err := this.Select("PING"); result != nil { - defer result.Free() - - if err == nil { - if retval, err := result.GetString(); retval == "PONG" { - return err // should be nil on success... - } else { - return errors.New("ERROR: Unexpected result (-1)") - } - } - return err - } else { - if err != nil { - return err - } - return errors.New("Got no result on Ping") - } -} - -// ListCommands lists all available server commands and returns them in an array of strings. -func (this *SQCloud) ListCommands() ([]string, error) { - return this.SelectStringList("LIST COMMANDS") -} - -// GetInfo fetches all SQLite Cloud Database server specific runtime informations and returns a SQCloudInfo structure. -func (this *SQCloud) GetInfo() (SQCloudInfo, error) { - info := SQCloudInfo{ - SQLiteVersion: "0.0.0", - SQCloudVersion: "0.0.0", - SQCloudBuildDate: time.Unix(0, 0), - SQCloudGitHash: "N/A", - ServerTime: time.Unix(0, 0), - ServerCPUs: 0, - ServerOS: "N/A", - ServerArchitecture: "N/A", - ServicePID: 0, - ServiceStart: time.Unix(0, 0), - ServicePort: 0, - SericeMultiplexAPI: "N/A", - } - - result, err := this.SelectKeyValues("LIST INFO") - //fmt.Printf("Result %v", result) - if err == nil { - for k, v := range result { - switch k { - case "sqlitecloud_version": - info.SQCloudVersion = v - case "sqlite_version": - info.SQLiteVersion = v - case "sqlitecloud_build_date": - info.SQCloudBuildDate, _ = time.Parse("Jan 2 2006", v) - case "sqlitecloud_git_hash": - info.SQCloudGitHash = v - case "os": - info.ServerOS = v - case "arch_bits": - info.ServerArchitecture = v - case "multiplexing_api": - info.SericeMultiplexAPI = v - case "listening_port": - info.ServicePort, _ = strconv.Atoi(v) - case "process_id": - info.ServicePID, _ = strconv.Atoi(v) - case "num_processors": - info.ServerCPUs, _ = strconv.Atoi(v) - case "startup_datetime": - info.ServiceStart, _ = time.Parse("2006-01-02 15:04:05", v) - case "current_datetime": - info.ServerTime, _ = time.Parse("2006-01-02 15:04:05", v) - case "nocluster": - info.ServiceNocluster, _ = strconv.Atoi(v) - case "nodeid": - info.ServiceNodeID, _ = strconv.Atoi(v) - case "tls": - info.TLS = v - case "tls_conn_version": - info.TLSConnVersion = v - case "tls_conn_cipher": - info.TLSConnCipher = v - case "tls_conn_cipher_strength": - info.TLSConnCipherStrength, _ = strconv.Atoi(v) - case "tls_conn_alpn_selected": - info.TLSConnAlpnSelected = v - case "tls_conn_servername": - info.TLSConnServername = v - case "tls_peer_cert_provided": - info.TLSConnCipherStrength, _ = strconv.Atoi(v) - case "tls_peer_cert_subject": - info.TLSPeerCertSubject = v - case "tls_peer_cert_issuer": - info.TLSPeerCertIssuer = v - case "tls_peer_cert_hash": - info.TLSPeerCertHash = v - case "tls_peer_cert_notbefore": - info.TLSPeerCertNotBefore, _ = time.Parse("2006-01-02 15:04:05", v) - case "tls_peer_cert_notafter": - info.TLSPeerCertNotAfter, _ = time.Parse("2006-01-02 15:04:05", v) - default: - } - } - } - return info, err -} - -// ListTables lists all tables in the selected database and returns them in an array of strings. -// If no database was selected with SQCloud.UseDatabase(), an error is returned. -func (this *SQCloud) ListTables() ([]string, error) { - return this.SelectStringListWithCol("LIST TABLES", 1) -} diff --git a/GO/sdk/test/compress_test.go b/GO/sdk/test/compress_test.go deleted file mode 100644 index 82f12c71..00000000 --- a/GO/sdk/test/compress_test.go +++ /dev/null @@ -1,126 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud GO SDK -// /// /// /// Version : 1.0.1 -// // /// /// /// Date : 2021/10/08 -// /// /// /// /// Author : Andreas Pfeil -// /// /// /// /// -// /// ////////// /// /// Description : SQLite Cloud server test -// //// /// /// Creates a table, inserts many -// //// ////////// /// values, enables compress and -// //// //// selectes all values. -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -package sqlitecloudtest - -import ( - "fmt" - "math/rand" - "net/url" - "testing" - - sqlitecloud "github.com/sqlitecloud/sdk" -) - -const testDbnameCompress = "test-gosdk-compress-db.sqlite" -const testCompressKey = "compress" -const testCompressValue = "LZ4" - -func TestCompress(t *testing.T) { - url, err := url.Parse(testConnectionString) - values := url.Query() - values.Add(testCompressKey, testCompressValue) - url.RawQuery = values.Encode() - - connstring := url.String() - // log.Printf("TestCompress %s", connstring) - - db, err := sqlitecloud.Connect(connstring) - if err != nil { - t.Fatal(err) - } - - defer db.Close() - - if err = db.CreateDatabase(testDbnameCompress, "", "UTF-8", true); err != nil { - t.Fatal(err) - } - - if err := db.UseDatabase(testDbnameCompress); err != nil { - t.Fatal(err.Error()) - } - - if err = db.Execute(`CREATE TABLE IF NOT EXISTS "TestCompress" (ID INTEGER PRIMARY KEY AUTOINCREMENT, Dummy TEXT(200) )`); err != nil { - t.Fatal(err) - } - - if err = db.Execute(`DELETE FROM TestCompress`); err != nil { - t.Fatal(err) - } - - if res, err := db.Select("GET CLIENT KEY COMPRESSION"); err != nil { - t.Fatal(err) - } else if res.GetString_() != "1" { - res.Dump() - t.Fatalf("Expected COMPRESSION = 1, got %s", res.GetString_()) - } - - rndStr := "" - for i := 0; i < 10; i++ { - rndStr = fmt.Sprintf("%s%d", rndStr, rand.Int()) - } - - nrows := 1000 - sql := "BEGIN; " - for rows := 0; rows < nrows; rows++ { - sql = sql + fmt.Sprintf("INSERT INTO TestCompress ( Dummy ) VALUES( '%s' ); ", rndStr) - } - sql = sql + "COMMIT;" - - // log.Printf("Execute INSERT: start") - if err = db.Execute(sql); err != nil { - t.Fatal(err) - } - // log.Printf("Execute INSERT: end") - - if err = db.Compress("lz4"); err != nil { - t.Fatal(err) - } - - res1, err := db.Select("SELECT * FROM TestCompress") - if err != nil { - t.Fatal(err) - } else if !res1.IsRowSet() { - t.Fatalf("Expected RowSet, got %v", res1.GetType()) - } else if res1.GetNumberOfRows() != uint64(nrows) { - t.Fatalf("Expected %d rows, got %d", nrows, res1.GetNumberOfRows()) - } - - if err = db.Compress("NO"); err != nil { - t.Fatal(err) - } - - if res2, err := db.Select("SELECT * FROM TestCompress"); err != nil { - t.Fatal(err) - } else if !res2.IsRowSet() { - t.Fatalf("Expected RowSet, got %v", res2.GetType()) - } else if res2.GetNumberOfRows() != uint64(nrows) { - t.Fatalf("Expected %d rows, got %d", nrows, res2.GetNumberOfRows()) - } else if res1.GetStringValue_(uint64(nrows)-1, 1) != res2.GetStringValue_(uint64(nrows)-1, 1) { - t.Fatalf("Expected value '%s', got '%s'", res1.GetStringValue_(uint64(nrows), 1), res2.GetStringValue_(uint64(nrows), 1)) - } - - // Checking Unuse Database - if err := db.UnuseDatabase(); err != nil { - t.Fatal(err.Error()) - } - - // Checking REMOVE DATABASE - if err := db.RemoveDatabase(testDbnameCompress, false); err != nil { - t.Fatal(err.Error()) - } -} diff --git a/GO/sdk/test/go.mod b/GO/sdk/test/go.mod deleted file mode 100644 index 538991ec..00000000 --- a/GO/sdk/test/go.mod +++ /dev/null @@ -1,16 +0,0 @@ -module github.com/sqlitecloud/sdk/test - -go 1.18 - -require github.com/sqlitecloud/sdk v0.0.0 - -require ( - github.com/google/go-cmp v0.5.9 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/pierrec/lz4 v2.6.1+incompatible // indirect - github.com/xo/dburl v0.12.4 // indirect - golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect - golang.org/x/term v0.1.0 // indirect -) - -replace github.com/sqlitecloud/sdk v0.0.0 => ../ diff --git a/GO/sdk/test/go.sum b/GO/sdk/test/go.sum deleted file mode 100644 index d28e55a3..00000000 --- a/GO/sdk/test/go.sum +++ /dev/null @@ -1,19 +0,0 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= -github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/xo/dburl v0.12.4 h1:mAIQjCNqCRtfytZNN0tZzK01rfng3n4Ei1s+H9lh61I= -github.com/xo/dburl v0.12.4/go.mod h1:K6rSPgbVqP3ZFT0RHkdg/M3M5KhLeV2MaS/ZqaLd1kA= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/GO/sdk/test/literals_test.go b/GO/sdk/test/literals_test.go deleted file mode 100644 index 26061066..00000000 --- a/GO/sdk/test/literals_test.go +++ /dev/null @@ -1,215 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud GO SDK -// /// /// /// Version : 1.2.0 -// // /// /// /// Date : 2021/10/14 -// /// /// /// /// Author : Andreas Pfeil -// /// /// /// /// -// /// ////////// /// /// Description : Simple SQLite Cloud server -// //// /// /// test. Creates a table, -// //// ////////// /// insertes some values, uses -// //// //// C-SDK's Dump function. -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -package sqlitecloudtest - -import ( - "testing" - - sqlitecloud "github.com/sqlitecloud/sdk" -) - -const testDbnameLiteral = "test-gosdk-literal-db.sqlite" - -func TestLiterals(t *testing.T) { - var db *sqlitecloud.SQCloud - var res *sqlitecloud.Result - var err error - - // start := time.Now() - if db, err = sqlitecloud.Connect(testConnectionString); err != nil { - t.Fatal("CONNECT: ", err.Error()) - } - defer db.Close() - // fmt.Printf("CONNECT %v\n", time.Since(start)) - - // start := time.Now() - if err := db.CreateDatabase(testDbnameLiteral, "", "", true); err != nil { // Database, Key, Encoding, NoError - t.Fatal("CREATE DATABASE: ", err.Error()) - } - // fmt.Printf("CREATE DATABASE %v\n", time.Since(start)) - - // start := time.Now() - if err := db.UseDatabase(testDbnameLiteral); err != nil { - t.Fatal("USE DATABASE: ", err.Error()) - } - // fmt.Printf("USE DATABASE %v\n", time.Since(start)) - - // start := time.Now() - switch res, err = db.Select("TEST NULL"); { // NULL - case err != nil: - t.Fatal("TEST NULL: ", err.Error()) - case res == nil: - t.Fatal("TEST NULL: nil result") - case !res.IsNULL(): - t.Fatal("TEST NULL: invalid type") - } - res.Free() - // fmt.Printf("TEST NULL %v\n", time.Since(start)) - - // start := time.Now() - switch res, err = db.Select("TEST STRING"); { // String Literal - case err != nil: - t.Fatal("TEST STRING: ", err.Error()) - case res == nil: - t.Fatal("TEST STRING: nil result") - case !res.IsString(): - t.Fatal("TEST STRING: invalid type") - } - res.Free() - // fmt.Printf("TEST STRING %v\n", time.Since(start)) - - // start := time.Now() - switch res, err = db.Select("TEST ZERO_STRING"); { // String Literal - case err != nil: - t.Fatal("TEST ZERO_STRING: ", err.Error()) - case res == nil: - t.Fatal("TEST ZERO_STRING: nil result") - case !res.IsString(): - t.Fatal("TEST ZERO_STRING: invalid type") - } - res.Free() - // fmt.Printf("TEST ZERO_STRING %v\n", time.Since(start)) - - // start := time.Now() - switch res, err = db.Select("TEST ERROR"); { // Error - case err == nil: - t.Fatal("TEST ERROR: Unknown command returned no error") - } - // fmt.Printf("TEST ERROR %v\n", time.Since(start)) - - // start := time.Now() - switch res, err = db.Select("TEST INTEGER"); { // Integer - case err != nil: - t.Fatal("TEST INTEGER: ", err.Error()) - case res == nil: - t.Fatal("TEST INTEGER: nil result") - case !res.IsInteger(): - t.Fatal("TEST INTEGER: invalid type") - } - res.Free() - // fmt.Printf("TEST INTEGER %v\n", time.Since(start)) - - // start := time.Now() - switch res, err = db.Select("TEST FLOAT"); { // Float - case err != nil: - t.Fatal("TEST FLOAT: ", err.Error()) - case res == nil: - t.Fatal("TEST FLOAT: nil result") - case !res.IsFloat(): - t.Fatal("TEST FLOAT: invalid type") - } - res.Free() - // fmt.Printf("TEST FLOAT %v\n", time.Since(start)) - - // start := time.Now() - switch res, err = db.Select("TEST BLOB"); { // BLOB - case err != nil: - t.Fatal("TEST BLOB: ", err.Error()) - case res == nil: - t.Fatal("TEST BLOB: nil result") - case !res.IsBLOB(): - t.Fatal("TEST BLOB: invalid type") - default: - if l := len(res.GetBuffer()); l != 1000 { - t.Fatalf("TEST BLOB: invalid blob len (Expected 1000, Got %d)", l) - } - } - res.Free() - // fmt.Printf("TEST BLOB %v\n", time.Since(start)) - - // TEST ROWSET_CHUNK is slow (i.e. 18s, because it sends 147 separated chunks, one for each row) - // start := time.Now() - switch res, err = db.Select("TEST ROWSET_CHUNK"); { // ROWSET - case err != nil: - t.Fatal("TEST ROWSET_CHUNK: ", err.Error()) - case res == nil: - t.Fatal("TEST ROWSET_CHUNK: nil result") - case !res.IsRowSet(): - t.Fatal("TEST ROWSET_CHUNK: invalid type") - } - res.Free() - // fmt.Printf("TEST ROWSET_CHUNK %v\n", time.Since(start)) - - // start := time.Now() - switch res, err = db.Select("TEST ROWSET"); { // ROWSET - case err != nil: - t.Fatal("TEST ROWSET: ", err.Error()) - case res == nil: - t.Fatal("TEST ROWSET: nil result") - case !res.IsRowSet(): - t.Fatal("TEST ROWSET: invalid type") - } - res.Free() - // fmt.Printf("TEST ROWSET %v\n", time.Since(start)) - - // start := time.Now() - switch res, err = db.Select("TEST JSON"); { // JSON - case err != nil: - t.Fatal("TEST JSON: ", err.Error()) - case res == nil: - t.Fatal("TEST JSON: nil result") - case !res.IsJSON(): - t.Fatal("TEST JSON: invalid type") - } - res.Free() - // fmt.Printf("TEST JSON %v\n", time.Since(start)) - - // start := time.Now() - switch res, err = db.Select("TEST COMMAND"); { // Command - case err != nil: - t.Fatal("TEST COMMAND: ", err.Error()) - case res == nil: - t.Fatal("TEST COMMAND: nil result") - case !res.IsCommand(): - t.Fatal("TEST COMMAND: invalid type") - default: - if res.GetString_() != "PING" { // should be ping - t.Fatalf("TEST COMMAND: invalid command (%s)", res.GetString_()) - } - } - res.Free() - // fmt.Printf("TEST COMMAND %v\n", time.Since(start)) - - // start := time.Now() - switch res, err = db.Select("TEST ARRAY"); { // ARRAY - case err != nil: - t.Fatal("TEST ARRAY: ", err.Error()) - case res == nil: - t.Fatal("TEST ARRAY: nil result") - case !res.IsArray(): - t.Fatal("TEST ARRAY: invalid type") - default: - if res.GetNumberOfRows() != 5 { - t.Fatalf("TEST ARRAY: invalid number of rows (Expected 5, Got %d)", res.GetNumberOfRows()) - } - } - res.Free() - // fmt.Printf("TEST ARRAY %v\n", time.Since(start)) - - // start := time.Now() - if err := db.UnuseDatabase(); err != nil { - t.Fatal("UNUSE DATABASE: ", err.Error()) - } - // fmt.Printf("UNUSE DATABASE %v\n", time.Since(start)) - - // start := time.Now() - if err := db.RemoveDatabase(testDbnameLiteral, false); err != nil { - t.Fatal("REMOVE DATABASE: ", err.Error()) - } - // fmt.Printf("REMOVE DATABASE %v\n", time.Since(start)) -} diff --git a/GO/sdk/test/pubsub_test.go b/GO/sdk/test/pubsub_test.go deleted file mode 100644 index 847deae2..00000000 --- a/GO/sdk/test/pubsub_test.go +++ /dev/null @@ -1,106 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud GO SDK -// /// /// /// Version : 1.0.0 -// // /// /// /// Date : 2021/10/11 -// /// /// /// /// Author : Andreas Pfeil -// /// /// /// /// -// /// ////////// /// /// Description : Simple PSUB Test -// //// /// /// -// //// ////////// /// -// //// //// -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -package sqlitecloudtest - -import ( - "encoding/json" - "fmt" - "reflect" - "testing" - "time" - - sqlitecloud "github.com/sqlitecloud/sdk" -) - -const testPubsubChannelName = "TestPubsubChannel" - -var testPubsubMessage = map[string]string{"msg_id": "12345", "msg_content": "this is the content"} - -func TestPubsub(t *testing.T) { - db1, err := sqlitecloud.Connect(testConnectionString) - if err != nil { - t.Fatal("Connect 1: ", err.Error()) - } - defer db1.Close() - - db2, err := sqlitecloud.Connect(testConnectionString) - if err != nil { - t.Fatal("Connect 2: ", err.Error()) - } - defer db2.Close() - - ch := make(chan string, 1) - db1.Callback = func(db *sqlitecloud.SQCloud, jsonString string) { - ch <- jsonString - } - - if _, err := db1.ListChannels(); err != nil { - t.Fatal("ListChannels: ", err.Error()) - } - - if err := db1.CreateChannel(testPubsubChannelName, true); err != nil { - t.Fatal("CreateChannel: ", err.Error()) - } - - if channels, err := db1.ListChannels(); err != nil { - t.Fatal("ListChannels: ", err.Error()) - } else if !contains(channels, testPubsubChannelName) { - t.Fatal("ListChannels: ", fmt.Sprintf("Channel %s not found in LIST CHANNELS", testPubsubChannelName)) - } - - if err := db1.Listen(testPubsubChannelName); err != nil { - t.Fatal("Listen: ", err.Error()) - } - - jsonStr, err := json.Marshal(testPubsubMessage) - if err != nil { - t.Fatal("Marshal: ", err.Error()) - } - - if err := db2.SendNotificationMessage(testPubsubChannelName, string(jsonStr)); err != nil { - t.Fatal("SendNotificationMessage: ", err.Error()) - } - - select { - case receivedStr := <-ch: - var receivedMap map[string]interface{} - json.Unmarshal([]byte(receivedStr), &receivedMap) - payload, found := receivedMap["payload"] - if !found { - t.Fatal("Received notification: missing payload") - } - - payloadStr, ok := payload.(string) - if !ok { - t.Fatal("Received notification: invalid payload") - } - var messageMap map[string]string - json.Unmarshal([]byte(payloadStr), &messageMap) - - if !reflect.DeepEqual(messageMap, testPubsubMessage) { - t.Fatalf("Received notification: Expected %v, Got %v", testPubsubMessage, messageMap) - } - - case <-time.After(1 * time.Second): - t.Fatal("Timeout") - } - - if err := db1.RemoveChannel(testPubsubChannelName); err != nil { - t.Fatal("RemoveChannel: ", err.Error()) - } -} diff --git a/GO/sdk/test/selectarray_test.go b/GO/sdk/test/selectarray_test.go deleted file mode 100644 index 2e574048..00000000 --- a/GO/sdk/test/selectarray_test.go +++ /dev/null @@ -1,103 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud GO SDK -// /// /// /// Version : 1.1.0 -// // /// /// /// Date : 2021/10/08 -// /// /// /// /// Author : Andrea Donetti -// /// /// /// /// -// /// ////////// /// /// Description : Test program for the -// //// /// /// SQLite Cloud internal -// //// ////////// /// server commands. -// //// //// -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -package sqlitecloudtest - -import ( - "testing" - - sqlitecloud "github.com/sqlitecloud/sdk" -) - -const testDbnameSelectArray = "test-gosdk-selectarray-db.sqlite" - -func TestSelectArray(t *testing.T) { - // Server API test - - config, err1 := sqlitecloud.ParseConnectionString(testConnectionString) - if err1 != nil { - t.Fatal(err1.Error()) - } - - db := sqlitecloud.New(*config) - err := db.Connect() - - if err != nil { - t.Fatalf(err.Error()) - } - - defer db.Close() - - // test select null string, it was causing a crash on the server - if res, err := db.Select(""); err != nil { - t.Fatal(err.Error()) - } else if !res.IsNULL() { - t.Fatalf("Expected NULL, got %v", res.GetType()) - } - - // Checking CREATE DATABASE - if err := db.ExecuteArray("CREATE DATABASE ? PAGESIZE ? IF NOT EXISTS", []interface{}{testDbnameSelectArray, 4096}); err != nil { - t.Fatal(err.Error()) - } - - // Checking USE DATABASE - if err := db.UseDatabase(testDbnameSelectArray); err != nil { // Database - t.Fatal(err.Error()) - } - - // Creating Table - if err := db.Execute("CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY, b)"); err != nil { - t.Fatal(err.Error()) - } - - // Deleting Table content - if err := db.Execute("DELETE FROM t1"); err != nil { - t.Fatal(err.Error()) - } - - // Adding rows to Table with ExecuteArray - if err := db.ExecuteArray("INSERT INTO t1 (b) VALUES (?), (?), (?), (?)", []interface{}{int(1), "text 2", 2.2, []byte("A")}); err != nil { - t.Fatal(err.Error()) - } - - // Select Table - if res, err := db.SelectArray("SELECT * FROM t1 WHERE a >= ?", []interface{}{1}); err != nil { - t.Fatal(err.Error()) - } else if res.GetNumberOfRows() != 4 { - t.Fatalf("Expected 4 rows, got %d", res.GetNumberOfRows()) - } else if res.GetNumberOfColumns() != 2 { - t.Fatalf("Expected 2 columns, got %d", res.GetNumberOfColumns()) - } else if s, _ := res.GetStringValue(0, 1); s != "1" { - t.Fatalf("Expected '1', got '%s'", s) - } else if s, _ := res.GetStringValue(1, 1); s != "text 2" { - t.Fatalf("Expected 'text 2', got '%s'", s) - } else if s, _ := res.GetStringValue(2, 1); s != "2.2" { - t.Fatalf("Expected '2.2', got '%s'", s) - } else if s, _ := res.GetStringValue(3, 1); s != "A" { - t.Fatalf("Expected 'A', got '%s'", s) - } - - // Checking Unuse Database - if err := db.UnuseDatabase(); err != nil { - t.Fatal(err.Error()) - } - - // Checking REMOVE DATABASE - if err := db.RemoveDatabase(testDbnameSelectArray, false); err != nil { - t.Fatal(err.Error()) - } -} diff --git a/GO/sdk/test/server_test.go b/GO/sdk/test/server_test.go deleted file mode 100644 index 7a7c2639..00000000 --- a/GO/sdk/test/server_test.go +++ /dev/null @@ -1,205 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud GO SDK -// /// /// /// Version : 1.1.0 -// // /// /// /// Date : 2021/10/08 -// /// /// /// /// Author : Andreas Pfeil -// /// /// /// /// -// /// ////////// /// /// Description : Test program for the -// //// /// /// SQLite Cloud internal -// //// ////////// /// server commands. -// //// //// -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -package sqlitecloudtest - -import ( - "fmt" - "strings" - "testing" - - sqlitecloud "github.com/sqlitecloud/sdk" -) - -const testDbnameServer = "test-gosdk-server-db.sqlite" - -func TestServer(t *testing.T) { - db, err := sqlitecloud.Connect(testConnectionString) - if err != nil { - t.Fatal("Connect: ", err.Error()) - } - - // Checking wrong AUTH - if err := db.Auth(testUsername, "wrong password"); err == nil { - t.Fatal("AUTH: Expected authorization failed, got authorized") - } - db.Close() - - // reopen the connection (it was closed because of the auth command with wrong credentials) - db, err = sqlitecloud.Connect(testConnectionString) - if err != nil { - t.Fatal(err.Error()) - } - defer db.Close() - - // Checking PING - if db.Ping() != nil { - t.Fatal(err.Error()) - } - - // Checking AUTH - if err := db.Auth(testUsername, testPassword); err != nil { - t.Fatal("Checking AUTH: ", err.Error()) - } - - // Checking CREATE DATABASE - if err := db.CreateDatabase(testDbnameServer, "", "", false); err != nil { // Database, Key, Encoding, NoError - t.Fatal("CREATE DATABASE: ", err.Error()) - } - - // Checking LIST DATABASES - if databases, err := db.ListDatabases(); err != nil { - t.Fatal("LIST DATABASES: ", err.Error()) - } else { - if len(databases) == 0 || !contains(databases, testDbnameServer) { - t.Fatal("LIST DATABASES: ", fmt.Sprintf("Database %s not found in LIST DATABASES", testDbnameServer)) - } - } - - // USE DATABASE - if err := db.UseDatabase(testDbnameServer); err != nil { // Database - t.Fatal("USE DATABASES: ", err.Error()) - } - - // Checking SET KEY - testKey := "TestKey" - testKeyValue := "1405" - if err := db.SetKey(testKey, testKeyValue); err != nil { // Key, Value - t.Fatal("SET KEY: ", err.Error()) - } - testKey = strings.ToLower(testKey) - - // LIST KEYS - if keys, err := db.ListKeys(); err != nil { - t.Fatal("LIST KEYS: ", err.Error()) - } else { - if _, found := keys[testKey]; !found { - t.Fatal("LIST KEY: ", fmt.Sprintf("Key %s not found in LIST KEYS", testKey)) - } - } - - // GET KEY - if val, err := db.GetKey(testKey); err != nil { - t.Fatal("GET KEY: ", err.Error()) - } else { - if val != testKeyValue { - t.Fatal("GET KEY: ", fmt.Sprintf("Expected value %s, Got %s.", testKeyValue, val)) - } - } - - // REMOVE KEY - if err := db.RemoveKey(testKey); err != nil { - t.Fatal("REMOVE KEY: ", err.Error()) - } else { - if keys, err := db.ListKeys(); err != nil { - t.Fatal("REMOVE KEY, LIST KEY: ", err.Error()) - } else { - if _, found := keys[testKey]; found { - t.Fatal("REMOVE KEY, LIST KEY: ", fmt.Sprintf("Key %s still found in LIST KEYS", testKey)) - } - } - } - - // LIST COMMANDS - if commands, err := db.ListCommands(); err != nil { - t.Fatal("LIST COMMANDS: ", err.Error()) - } else { - if len(commands) == 0 { - t.Fatal("LIST COMMANDS: Invalid result (0 rows).") - } - } - - // LIST CONNECTIONS - if connections, err := db.ListConnections(); err != nil { - t.Fatal("LIST CONNECTIONS: ", err.Error()) - } else { - if len(connections) == 0 { - t.Fatal("LIST CONNECTIONS: Invalid result (no connections).") - } - } - - // LIST DATABASE CONNECTIONS ()...") - if connections, err := db.ListDatabaseConnections(testDbnameServer); err != nil { - t.Fatal("LIST DATABASE CONNECTIONS: ", err.Error()) - } else { - if len(connections) == 0 { - t.Fatal("LIST DATABASE CONNECTIONS: Invalid result (0 connections).") - } - } - - // LIST INFO - if _, err := db.GetInfo(); err != nil { - t.Fatal("LIST INFO: ", err.Error()) - } - - // CREATE TABLE - testTableServer := "TestTable" - if err := db.Execute(fmt.Sprintf("CREATE TABLE IF NOT EXISTS '%s' (a INTEGER PRIMARY KEY, b)", testTableServer)); err != nil { - t.Fatal("CREATE TABLE: ", err.Error()) - } - - // LIST TABLES - if tables, err := db.ListTables(); err != nil { - t.Fatal("LIST TABLES: ", err.Error()) - } else { - if len(tables) < 1 || !contains(tables, testTableServer) { - t.Fatal("LIST TABLES: ", fmt.Sprintf("Table %s not found in LIST TABLES", testTableServer)) - } - } - - // LIST PLUGINS - if _, err := db.ListPlugins(); err != nil { - t.Fatal("LIST PLUGINS: ", err.Error()) - } - - // LIST CLIENT KEYS - if ckeys, err := db.ListClientKeys(); err != nil { - t.Fatal("LIST CLIENT KEYS: ", err.Error()) - } else if len(ckeys) == 0 { - t.Fatal("LIST CLIENT KEYS: Invalid result") - } - - // LIST NODES - if nodes, err := db.ListNodes(); err != nil { - t.Fatal("LIST NODES: ", err.Error()) - } else { - if len(nodes) == 0 { - t.Fatal("LIST NODES: Ivalid result.") - } - } - - // LIST DATABASE KEYS - if _, err := db.ListDatabaseKeys(testDbnameServer); err != nil { - t.Fatal("LIST DATABASE KEYS:", err.Error()) - } - - // UNUSE DATABASE - if err := db.UnuseDatabase(); err != nil { - t.Fatal("UNUSE DATABASE:", err.Error()) - } - - // REMOVE DATABASE - if err := db.RemoveDatabase(testDbnameServer, false); err != nil { // Database, NoError - t.Fatal("REMOVE DATABASES: ", err.Error()) - } - - // fmt.Printf( "Checking CLOSE CONNECTION..." ) - // if err := db.CloseConnection( "14" ); err != nil { // ConnectionID - // fail( err.Error() ) - // } - // fmt.Printf( "ok.\r\n" ) -} diff --git a/GO/sdk/test/server_testcases.txt b/GO/sdk/test/server_testcases.txt deleted file mode 100644 index f8a88df8..00000000 --- a/GO/sdk/test/server_testcases.txt +++ /dev/null @@ -1,37 +0,0 @@ -----------------------------------------------------------| - command | -----------------------------------------------------------| -💤 ADD [LEARNER] NODE % ADDRESS % CLUSTER % SNAPSHOT % | -✅ AUTH [USER %] [PASS %] | -💤 CLOSE CONNECTION % | -✅ CREATE DATABASE % [KEY %] [ENCODING %] [IF NOT EXISTS] | -💤 DISABLE PLUGIN % | -✅ REMOVE DATABASE % [IF EXISTS] | -✅ REMOVE KEY % | -💤 ENABLE PLUGIN % | -✅ GET DATABASE [ID] | -✅ GET KEY % | -✅ LIST COMMANDS | -✅ LIST CONNECTIONS | -✅ LIST DATABASE CONNECTIONS [ID] % | -✅ LIST DATABASES | -✅ LIST INFO | -💤 LIST NODES | -✅ LIST PLUGINS | -✅ LIST TABLES | -✅ LIST [%] KEYS | -✅ LIST CLIENT KEYS | -✅ LIST DATABASE KEYS | -💤 LISTEN % | -💤 NOTIFY % [%] | -✅ PING | -💤 REMOVE NODE % | -✅ SET KEY % TO % | -💤 UNLISTEN % | -✅ UNUSE DATABASE | -✅ USE DATABASE % | -----------------------------------------------------------| - -✅ = Implemented, working. -💣 = Implemented, not working, bug. -💤 = Waiting for Server support or postponed. \ No newline at end of file diff --git a/GO/sdk/test/test_commons.go b/GO/sdk/test/test_commons.go deleted file mode 100644 index 0a2508c3..00000000 --- a/GO/sdk/test/test_commons.go +++ /dev/null @@ -1,23 +0,0 @@ -package sqlitecloudtest - -import "flag" - -const testConnectionStringLocalhost = "sqlitecloud://admin:admin@localhost:8860?tls=SQLiteCloudCA" - -const testUsername = "admin" -const testPassword = "admin" - -var testConnectionString string = testConnectionStringLocalhost - -func init() { - flag.StringVar(&testConnectionString, "server", testConnectionStringLocalhost, "Connection String") -} - -func contains[T comparable](s []T, e T) bool { - for _, v := range s { - if v == e { - return true - } - } - return false -} diff --git a/GO/sdk/value.go b/GO/sdk/value.go deleted file mode 100644 index 3d2a65dc..00000000 --- a/GO/sdk/value.go +++ /dev/null @@ -1,167 +0,0 @@ -// -// //// SQLite Cloud -// //////////// /// -// /// /// /// Product : SQLite Cloud GO SDK -// /// /// /// Version : 1.0.1 -// // /// /// /// Date : 2021/10/13 -// /// /// /// /// Author : Andreas Pfeil -// /// /// /// /// -// /// ////////// /// /// Description : GO Methods related to the -// //// /// /// Value class. -// //// ////////// /// -// //// //// -// //// ///// -// /// Copyright : 2021 by SQLite Cloud Inc. -// -// -----------------------------------------------------------------------TAB=2 - -package sqlitecloud - -import ( - "errors" - "strconv" - "time" -) - -const ( - CMD_STRING = '+' - CMD_ZEROSTRING = '!' - CMD_ERROR = '-' - CMD_INT = ':' - CMD_FLOAT = ',' - CMD_ROWSET = '*' - CMD_ROWSET_CHUNK = '/' - CMD_JSON = '#' - CMD_RAWJSON = '{' - CMD_NULL = '_' - CMD_BLOB = '$' - CMD_COMPRESSED = '%' - CMD_PUBSUB = '|' - CMD_COMMAND = '^' - CMD_RECONNECT = '@' - CMD_ARRAY = '=' -) - -const ( - NO_EXTCODE = 0 - NO_OFFCODE = -1 -) - -type Value struct { - Type byte // _ + # : , $ ^ @ - /// Types that are not in this Buffer: ROWSET, PUBSUB - Buffer []byte -} - -func (this *Value) GetType() byte { - switch this.Type { - case CMD_ZEROSTRING: - return CMD_STRING // Translate C-String to String - case CMD_RAWJSON: - return CMD_JSON // Translate RAW-JSON to JSON - case CMD_ROWSET, CMD_ROWSET_CHUNK: - return CMD_ROWSET // Translate to ROWSET - case CMD_ARRAY: - return CMD_ARRAY // Array - case CMD_INT, CMD_FLOAT, CMD_STRING, CMD_JSON, CMD_BLOB, - CMD_COMMAND, CMD_RECONNECT, CMD_ERROR, CMD_PUBSUB, CMD_NULL: - if this.Buffer == nil { - return CMD_NULL // unset buffer translates to NULL - } else { - return this.Type - } - default: - return 0 - } -} -func (this *Value) IsSet() bool { return this.GetType() != 0 } -func (this *Value) IsNULL() bool { return this.GetType() == CMD_NULL } -func (this *Value) IsString() bool { return this.GetType() == CMD_STRING } -func (this *Value) IsJSON() bool { return this.GetType() == CMD_JSON } -func (this *Value) IsInteger() bool { return this.GetType() == CMD_INT } -func (this *Value) IsFloat() bool { return this.GetType() == CMD_FLOAT } -func (this *Value) IsBLOB() bool { return this.GetType() == CMD_BLOB } -func (this *Value) IsPSUB() bool { return this.GetType() == CMD_PUBSUB } -func (this *Value) IsCommand() bool { return this.GetType() == CMD_COMMAND } -func (this *Value) IsReconnect() bool { return this.GetType() == CMD_RECONNECT } -func (this *Value) IsError() bool { return this.GetType() == CMD_ERROR } -func (this *Value) IsRowSet() bool { return this.GetType() == CMD_ROWSET } -func (this *Value) IsArray() bool { return this.GetType() == CMD_ARRAY } - -func (this *Value) IsText() bool { - return this.IsString() || this.IsInteger() || this.IsFloat() || this.IsBLOB() -} - -func (this *Value) GetLength() uint64 { return uint64(len(this.Buffer)) } -func (this *Value) GetBuffer() []byte { return this.Buffer } // Also good for BLOB - -func (this *Value) GetString() string { return string(this.GetBuffer()) } // Also good for: JSON, BLOB, Command, Reconnect -func (this *Value) IsOK() bool { return this.GetType() == '+' && this.GetString() == "OK" } - -func (this *Value) GetInt32() (int32, error) { - switch value, err := strconv.ParseInt(this.GetString(), 0, 32); { - case err != nil: - return 0, err - default: - return int32(value), nil - } -} -func (this *Value) GetInt64() (int64, error) { return strconv.ParseInt(this.GetString(), 0, 64) } - -func (this *Value) GetFloat32() (float32, error) { - switch value, err := strconv.ParseFloat(this.GetString(), 32); { - case err != nil: - return 0, err - default: - return float32(value), nil - } -} -func (this *Value) GetFloat64() (float64, error) { return strconv.ParseFloat(this.GetString(), 64) } - -// GetError returns the ErrorCode, ExtErrorCode, ErrorOffset, ErrorMessage -// and the error object of the receiver -func (this *Value) GetError() (int, int, int, string, error) { - ErrorCode := 0 - ExtErrorCode := NO_EXTCODE - ErrorOffset := NO_OFFCODE - nColons := 0 - for i, LEN, buffer := uint64(0), this.GetLength(), this.GetBuffer(); i < LEN; i++ { - switch c := buffer[i]; c { - case ':': - nColons++ - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - switch nColons { - case 0: - ErrorCode = ErrorCode*10 + int(c) - int('0') - case 1: - ExtErrorCode = ExtErrorCode*10 + int(c) - int('0') - case 2: - if ErrorOffset == NO_OFFCODE { - ErrorOffset = 0 - } - ErrorOffset = ErrorOffset*10 + int(c) - int('0') - } - - default: - return ErrorCode, ExtErrorCode, ErrorOffset, string(buffer[i+1:]), nil - } - } - return -1, NO_EXTCODE, NO_OFFCODE, this.GetString(), errors.New("Invalid error format") -} - -// Aux Methods - -func (this *Value) GetSQLDateTime() (time.Time, error) { - for _, format := range []string{ - "2006-01-02 15:04:05", - "2006-01-02", - "15:04:05", - } { - switch datetime, err := time.Parse(format, this.GetString()); { - case err != nil: - return time.Unix(0, 0), err - default: - return datetime, nil - } - } - return time.Unix(0, 0), errors.New("Invalid Format") -} diff --git a/JS/.npmignore b/JS/.npmignore deleted file mode 100644 index d61e3d98..00000000 --- a/JS/.npmignore +++ /dev/null @@ -1,22 +0,0 @@ -# dependencies -node_modules -**/node_modules -**/**/node_modules - -# production - -public -**/public -**/**/public - - -# env -.env -**/.env -**/**/.env - -# example -example - -# JS local example -**/example/local-sqlite-cloud-chat \ No newline at end of file diff --git a/JS/README.md b/JS/README.md deleted file mode 100644 index 738b877d..00000000 --- a/JS/README.md +++ /dev/null @@ -1,465 +0,0 @@ -# SQLite Cloud Javascript Client SDK - -Official SDK repository for SQLite Cloud databases and nodes. - -This Javascript SDK allows a WebApp to communicate with an SQLite Cloud cluster using 2 WebSocket. -* A **main WebSocket** used to: - * execute commands - * get channels list - * create a new channel - * remove an existing channel - * send notifications to a specific channel - * listen a channel - * listen a database table - * unlisten a channel - * unlisten a table - -* **Pub/Sub WebSocket** used to (only after at least one channel/table listening sent command from **main WebSocket**): - * receive notifications sent by others users - - -## How to use -First of all you have to get your `APP_KEY` and `PROJECT_ID` associated to an SQLite Cloud cluster from the [SQLiteCloud dashboard](https://dashboard.sqlitecloud.io/). - -Once you have your credentials, you can create a new **main WebSocket**. Every instance of the Javascript Client SDK allows to create a **main WebSocket**. - -As describe above **main WebSocket** is used for all outgoing communications from your WebApp. - -It is very important to emphasize that once you have created and registered channels or tables for PUB/SUB communications, if you are only interested in receiving messages, you can close the **main WebSocket**, leaving open only the **Pub/Sub WebSocket**. - - -## Example -For a simple, but comprehensive example of the functionality of this SDK check out this [project](https://test-js-sdk.sqlitecloud.io/) and the [relative code](https://github.com/sqlitecloud/sdk/tree/master/JS/example/test-js-sdk). - -For a sample WebApp using this SDK that demonstrates the power of the Pub/Sub capabilities built into SQLite Cloud, check out this [SQLite Cloud Chat](https://chat.sqlitecloud.io/) and the [relative code](https://github.com/sqlitecloud/sdk/tree/master/JS/example/sqlite-cloud-chat). - - -## Topics - -The following topics are covered: - -* [Installation](https://github.com/sqlitecloud/sdk/tree/master/JS#installation) - * [Web](https://github.com/sqlitecloud/sdk/tree/master/JS#web) -* [Initialization](https://github.com/sqlitecloud/sdk/tree/master/JS#initialization) -* [Configuration -](https://github.com/sqlitecloud/sdk/tree/master/JS#configuration) -* [SDK Methods -](https://github.com/sqlitecloud/sdk/tree/master/JS#sdk-methods) - * [Connection](https://github.com/sqlitecloud/sdk/tree/master/JS#connection) - * [Close](https://github.com/sqlitecloud/sdk/tree/master/JS#close) - * [Main WebSocket connection state](https://github.com/sqlitecloud/sdk/tree/master/JS#main-websocket-connection-state) - * [PUB/SUB WebSocket connection state](https://github.com/sqlitecloud/sdk/tree/master/JS#pubsub-websocket-connection-state) - * [List Channels](https://github.com/sqlitecloud/sdk/tree/master/JS#list-channels) - * [Create Channel](https://github.com/sqlitecloud/sdk/tree/master/JS#create-channel) - * [Remove Channel](https://github.com/sqlitecloud/sdk/tree/master/JS#remove-channel) - * [Exec Command](https://github.com/sqlitecloud/sdk/tree/master/JS#exec-command) - * [Notify](https://github.com/sqlitecloud/sdk/tree/master/JS#notify) -* [Developing](#) - * [Building](#) - - - -## Supported platforms - -* Web - * We test against Chrome, Firefox and Safari. - * Works in web pages - - -## Installation - -### Web - -You can install the library via: - -#### NPM (or Yarn) - -You can use any NPM-compatible package manager, including NPM itself and Yarn. - -```bash -npm install sqlitecloud-sdk -``` - -Then: - -```javascript -import SQLiteCloud from 'sqlitecloud-sdk'; -``` - -Or, if you're not using ES6 modules: - -```javascript -const SQLiteCloud = require('sqlitecloud-sdk'); -``` - -#### CDN - -```html - -``` - -Then: - -```javascript -const SQLiteCloud = window.SQLiteCloud; -``` - - -## Initialization - -```js -const client = new SQLiteCloud(PROJECT_ID, API_KEY); -``` - -Optionally during initialization you can pass two callbacks functions: -* `onErrorCallback` called on WebSocket error event -* `onCloseCallback` called on WebSocket close event - -```js -const onErrorCallback = function (event, msg) { - console.log("WebSocket onError callback:" + msg); - console.log(event); -} -const onCloseCallback = function (msg) { - console.log("WebSocket OnClose callback:" + msg); -} -const client = new SQLiteCloud(config.PROJECT_ID, config.API_KEY, onErrorCallback, onCloseCallback); - -``` - -You can get your APP_KEY and PROJECT_ID from the [SQLiteCloud dashboard](https://dashboard.sqlitecloud.io/). - - -## Configuration - -After initializazion it is possibile to configure your client. - -#### `SQLiteCloud.setRequestTimeout` (Int value in milliseconds) -Default value is `3000 ms` - -#### `SQLiteCloud.setFilterSentMessages` (Boolean) -Default value is `false` - -If `true` during PUB/SUB communications library not return messages sent by the user. - - -## SDK Methods - -**Method**|**Description** ---- | --- -`async connect()`|Creates a new **main WebSocket*. Returns how creation process completed. -`close(closePubSub = true)`|By default, closes both the **main WebSocket** and the **Pub/Sub WebSocket**. If invoked with `closePubSub = false`, closes only the **main WebSocket**. Returns how closing process completed. -`connectionState`|Returns the actual state of the **main WebSocket**. -`pubSubState`|Returns the actual state of the **Pub/Sub WebSocket**. -`async listChannels()`|Uses **main WebSocket** to request the list of all active channels for the current SQLite Cloud cluster. On command execution success returns the channels list, if not return error. -`async createChannel(channelName, ifNotExist = true)`|Uses **main WebSocket** to create a new channel with the specified name. On command exectution success returns the `response`, if not return error. -`async removeChannel(channelName)`|Uses **main WebSocket** to remove the channel with the specified name. On command exectution success returns the `response`, if not return error. -`async exec(command)`|Uses **main WebSocket** to send commands. On command execution success returns the `response`, if not return error. -`async notify(channel, payload)`|Uses **main WebSocket** to send notification to an avaible channel for the current SQLite Cloud cluster. On notification exectution success returns the `response`, if not return error. -`async listenChannel(channel, callback)`|Uses **main WebSocket** to start listening for incoming message on the selected channel. On the first listenChannel request the SDK creates the **Pub/Sub WebSocket**. On the following listenChannel request the SDK simply adds the new subscription to the supscriptionStack. For each listend channel a callback is registered to be invoked when a new message arrives. The callback can be different for each channel. On listen execution success returns the `response`, if not return error. -`async listenTable(table, callback)`|Uses **main WebSocket** to start listening for incoming message on the selected table. On the first listenTable request the SDK creates the **Pub/Sub WebSocket**. On the following listenTable request the SDK simply adds the new subscription to the supscriptionStack. For each listend table a callback is registered to be invoked when a new message arrives. The callback can be different for each table. On listen execution success returns the `response`, if not return error. -`async unlistenChannel(channel)`|Uses **main WebSocket** to stop listening for incoming message on the selected channel. On unlisten execution success returns the `response`, if not return error. -`async unlistenTable(table)`|Uses **main WebSocket** to stop listening for incoming message on the selected table. On unlisten execution success returns the `response`, if not return error. -`requestsStackState()`|Returns the list of pending requests. -`subscriptionsStackState()`|Returns the list of active subscriptions. - - - -### Connection - -#### `SQLiteCloud.connect()` - -After initializazion and configuration you can connect invoking the `async` method `SQLiteCloud.connect()`. - -```js -async function () { - const connectionResponse = await client.connect(); - if (connectionResponse.status == 'success') { - console.log(connectionResponse.data.message); - } else { - console.log(connectionResponse.data.message); - } -} -``` - -This method returns the following object: - -```js -//success or warning response -/* -connectionResponse = { - status: "success" | "warning" - data: { - message: "..." - } -} -*/ - -//error response -/* -connectionResponse = { - status: "error" - data: error -} -*/ - -``` - -### Close - -#### `SQLiteCloud.close()` - -To close **main WebSocket** and **PUB/SUB WebSocket** you can invoking the method `SQLiteCloud.close()`. - -```js -const close = function (closeAll) { - //try to close websocket connection - var closeResponse = client.close(closeAll); - //check how websocket close completed - console.log(closeResponse); - closeResult.innerHTML = closeResponse.data.message; - if (closeResponse.status == 'success') { - //successful websocket close - logThis(closeResponse.data.message); - } else { - //error on websocket close - logThis(closeResponse.data.message); - } -} -//close both "main WebSocket" and "PUB/SUB WebSocket" -close(true); -//close only "main WebSocket" leaving open "PUB/SUB WebSocket" to receive incoming messages on subscripted channels and tables -close(true); -``` - -This method returns the following object: - -```js -//success or error response -/* -connectionResponse = { - status: "success" | "error" - data: { - message: "..." - } -} -*/ -``` - -### Main WebSocket connection state - -#### `SQLiteCloud.connectionState` - -You can monitor the state of **main WebSocket** invoking the method `SQLiteCloud.connectionState`. - -```js -setInterval(function () { - console.log(client.connectionState); -}, 500) -``` - -### PUB/SUB WebSocket connection state - -#### `SQLiteCloud.pubSubState` - -You can monitor the state of **PUB/SUB WebSocket** invoking the method `SQLiteCloud.connectionState`. - -```js -setInterval(function () { - console.log(client.pubSubState); -}, 500) -``` - -### List Channels - -#### `SQLiteCloud.listChannels()` - -You can request the list of all active channels for the the current SQLite Cloud cluster invoking the `async` method `SQLiteCloud.listChannels()`. - -```js -async function () { - const listChannelsResponse = await client.listChannels(); - if (listChannelsResponse.status == 'success') { - console.log(listChannelsResponse.data); - var channels = listChannelsResponse.data.rows; - for (var i = 0; i < channels.length; i++) { - console.log(channels[i]); - } - } else { - console.log(listChannelsResponse.data.message); - } -} -``` - -This method returns the following object: - -```js -//success or warning response -/* -connectionResponse = { - status: "success" - data: { - columns: ['chname'], - rows: [ - {chname: ch0}, - {chname: ch1}, - {chname: ch2} - ], - } -} -*/ - -//error response -/* -connectionResponse = { - status: "error" - data: { - message: "..." - } -} -*/ - -``` - -### Create Channel - -#### `SQLiteCloud.createChannel()` - -You can request the creation of a new channel for the the current SQLite Cloud cluster invoking the `async` method `SQLiteCloud.createChannel()`. - -```js -const createChannel = async function (channelName) { - const createChannelResponse = await client.createChannel(channelName); - if (createChannelResponse.status == 'success') { - console.log(createChannelResponse.data); - } else { - console.log(createChannelResponse.data.message); - } -} -const newChannel = "test-ch"; -createChannel(newChannel); -``` - -This method returns the following object: - -```js -//success or warning response -/* -createChannelResponse = { - status: "success" - data: "OK" -} -*/ - -//error response -/* -createChannelResponse = { - status: "error" - data: { - message: "..." - } -} -*/ - -``` - -### Remove Channel - -#### `SQLiteCloud.removeChannel()` - -You can request the removal of a channel for the the current SQLite Cloud cluster invoking the `async` method `SQLiteCloud.removeChannel()`. - -```js -const removeChannel = async function (channelName) { - const removeChannelResponse = await client.removeChannel(channelName); - if (removeChannelResponse.status == 'success') { - console.log(removeChannelResponse.data); - } else { - console.log(removeChannelResponse.data.message); - } -} -const removeChannel = "test-ch"; -removeChannel(removeChannel); -``` - -This method returns the following object: - -```js -//success or warning response -/* -removeChannelResponse = { - status: "success" - data: "OK" -} -*/ - -//error response -/* -removeChannelResponse = { - status: "error" - data: { - message: "..." - } -} -*/ - -``` - -### Exec Command - -#### `SQLiteCloud.exec()` - -You can execute a command for the the current SQLite Cloud cluster invoking the `async` method `SQLiteCloud.exec()`. - -```js -const execCommand = async function (command) { - const execCommandResponse = await client.exec(command); - if (execCommandResponse.status == 'success') { - console.log(execCommandResponse.data); - } else { - console.log(execCommandResponse.data.message); - } -} -const command = "USE DATABASE db1.sqlite; LIST TABLES PUBSUB"; -execCommand(command); -``` - -This method returns the following object: - -```js -//success response -/* -execCommandResponse = { - status: "success" - data: [depend on submitted command] -} -*/ - -//error response -/* -execCommandResponse = { - status: "error" - data: { - code: [int value] - message: "..." - } -} -*/ - -``` - -### Notify - -#### `SQLiteCloud.notify()` - -You can notify a message on an avaible channel for the the current SQLite Cloud cluster invoking the `async` method `SQLiteCloud.notify()`. - -```js - -``` - -This method returns the following object: - -```js - -``` diff --git a/JS/dist/1.0/sqlitecloud-sdk.js b/JS/dist/1.0/sqlitecloud-sdk.js deleted file mode 100644 index f0a7af6a..00000000 --- a/JS/dist/1.0/sqlitecloud-sdk.js +++ /dev/null @@ -1,1052 +0,0 @@ -/*! - * SQLite Cloud SDK v1.0.6 - * https://sqlitecloud.io/ - * - * Copyright 2023, SQLite Cloud - * Released under the MIT licence. - */ - -(function webpackUniversalModuleDefinition(root, factory) { - if(typeof exports === 'object' && typeof module === 'object') - module.exports = factory(); - else if(typeof define === 'function' && define.amd) - define([], factory); - else if(typeof exports === 'object') - exports["SQLiteCloud"] = factory(); - else - root["SQLiteCloud"] = factory(); -})(this, () => { -return /******/ (() => { // webpackBootstrap -/******/ var __webpack_modules__ = ({ - -/***/ 763: -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -module.exports = __webpack_require__(542)["default"]; - - -/***/ }), - -/***/ 542: -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; - -// EXPORTS -__webpack_require__.d(__webpack_exports__, { - "default": () => (/* binding */ SQLiteCloud) -}); - -;// CONCATENATED MODULE: ./src/core/messages.js -const msg = { - wsConnectOk: "webSocket connection is active.", - wsAlreadyConnected: "webSocket connection has already been created.", - wsCantConnectedWsPubSubExist: "webSocket connection cannot be created because a pubSub connection is already active.", - wsConnectError: "websocket connection not established. Check your internet connection, project ID and API key.", - wsClosingWsPubSubClosingProcess: "closing of the WebSocket and pubSub started.", - wsClosingProcess: "closing of the WebSocket started.", - wsPubSubClosingProcess: "closing of the pubSub started.", - wsCloseComplete: "webSocket connection has been closed.", - wsPubSubClosingProcess: "closing of the pubSub started.", - wsPubSubCloseComplete: "pubSub connection has been closed.", - wsClosingError: "there is no WebSocket that can be closed.", - wsCloseByClient: "main WebSocket connection closed by client.", - wsPubSubCloseByClient: "pubSub WebSocket connection closed by client.", - wsNotExist: "websocket connection not exist.", - wsOnError: "websocket connection error.", - wsPubSubOnError: "pubSub connection error.", - wsConnecting: "CONNECTING", - wsOpen: "OPEN", - wsClosing: "CLOSING", - wsClosed: "CLOSED", - wsPubSubNotExist: "pubSub connection not exist.", - wsPubSubConnecting: "CONNECTING", - wsPubSubOpen: "OPEN", - wsPubSubClosing: "CLOSING", - wsPubSubClosed: "CLOSED", - wsExecErrorNoConnection: "you need to create a WebSocket connection. Use the connect method.", - wsNotifyErrorNoConnection: "you need to create a WebSocket connection. Use the connect method.", - wsListenError: { - alreadySubscribed: "registration already made to the channel:", - errorNoConnection: "you need to create a WebSocket connection. Use the connect method.", - }, - wsUnlistenError: { - missingSubscritption: "it is not possible to unlisten unregistered channel:", - errorNoConnection: "you have closed the WebSocket connection, it is no longer possible to unlisten channels.", - }, - wsTimeoutError: "the request timed out. ID:", - createChannelErr:{ - mandatory: "channelName is mandatory", - string: "channelName has to be a string" - }, - removeChannelErr:{ - mandatory: "channelName is mandatory", - string: "channelName has to be a string" - } -} -;// CONCATENATED MODULE: ./src/core/sqlitecloud.js - - - - -class SQLiteCloud { - /* PRIVATE PROPERTIES */ - - /* - #ws private property stores the websocket used: - - to send "exec" type request - - to send "pubsub" subscription request - - to receive response to "exec" type request - - User receives the responses to his requests reading the result of a Promise. - */ - #ws = null; - - /* - #wsPubSub private property stores the websocket used to receive pubSub messages. - #wsPubSubUrl private property stores the websocket url - #uuid private property stores the user identifier. This can be used to not received messages sent by the current user - When a new message is received, based on the channel, is selected the callbacks to be called cycling through subscriptionsStack property - */ - #wsPubSub = null; - #wsPubSubUrl = null; - #uuid = null; - - /* - #requestsStack private property stores the list of pending requests, in this way the user can send multiple parallel requests. - For each request an object that contains the following is stored: - { - id: //unique id associated with the request - onRequestTimeout: //function called when the request times out - resolve: resolve, //function called when the Promise resolve - reject: reject //function called when the Promise reject - } - */ - #requestsStack = new Map(); - - /* - #subscriptionsStack private property stores the list of pubSub subscriptions. - For each subscription an object that contains the following is stored: - { - channel: //the name of the channel you are subscribed to - callback: //the function called when a new message arrives - } - */ - #subscriptionsStack = new Map(); - - /* PUBLIC PROPERTIES */ - /* - requestTimeout property stores the time available to receive a response before the request times out. - filterSentMessages the library not return messages sent by the user - */ - requestTimeout = 3000; - filterSentMessages = false; - - - /* CONSTRUCTOR */ - /* - SQLiteCloud class constructor receives: - - project ID (required) - - api key (required) - - webSocket callback event (optional) - */ - constructor(projectID, apikey, onError = null, onClose = null) { - this.url = `wss://web1.sqlitecloud.io:8443/api/v1/${projectID}/ws?apikey=${apikey}`; - this.onError = onError; - this.onClose = onClose; - } - - /* PUBLIC METHODS */ - /* - setRequestTimeout method allows the user to change the request timeout value - */ - setRequestTimeout(value) { - this.requestTimeout = value; - } - - /* - setFilterSentMessages method allows the user to filter or not sent messagess - */ - setFilterSentMessages(value) { - this.filterSentMessages = value; - } - - - /* - connect method opens websocket connection - */ - async connect() { - if (this.#ws == null) { - if (this.#wsPubSub == null) { - try { - this.#ws = await this.#connectWs(this.url, msg.wsConnectError); - //register the error event on websocket - this.#ws.addEventListener('error', this.#onErrorWs); - //register the close event on websocket - this.#ws.addEventListener('close', this.#onCloseWs); - return { - status: "success", - data: { - message: msg.wsConnectOk - } - } - } catch (error) { - return { - status: "error", - data: error - }; - } - } else { - return { - status: "warning", - data: { - message: msg.wsCantConnectedWsPubSubExist - } - }; - } - } else { - return { - status: "warning", - data: { - message: msg.wsAlreadyConnected - } - } - } - } - - /* - close method closes websocket connection and if exist pubSub websocket connection - - closePubSub = true (default true), this method close both WebSocket - - closePubSub = false this method close only main WebSocket - */ - close(closePubSub = true) { - if (closePubSub) { - if (this.#wsPubSub !== null && this.#ws !== null) { - this.#wsPubSub.close(1000, msg.wsPubSubCloseByClient); - this.#subscriptionsStack = new Map(); - this.#ws.close(1000, msg.wsCloseByClient); - return ( - { - status: "success", - data: { - message: msg.wsClosingWsPubSubClosingProcess - } - } - ) - } else if (this.#wsPubSub == null && this.#ws !== null) { - this.#ws.close(1000, msg.wsCloseByClient); - return ( - { - status: "success", - data: { - message: msg.wsClosingProcess - } - } - ) - } else if (this.#wsPubSub != null && this.#ws == null) { - this.#subscriptionsStack = new Map(); - this.#wsPubSub.close(1000, msg.wsCloseByClient); - return ( - { - status: "success", - data: { - message: msg.wsPubSubClosingProcess - } - } - ) - } else { - return ( - { - status: "error", - data: { - message: msg.wsClosingError - } - } - ) - } - } - if (!closePubSub) { - if (this.#ws !== null) { - this.#ws.close(1000, msg.wsCloseByClient); - return ( - { - status: "success", - data: { - message: msg.wsClosingProcess - } - } - ) - } else { - return ( - { - status: "error", - data: { - message: msg.wsClosingError - } - } - ) - } - } - } - - /* - connectionState method returns the actual state of websocket connection - */ - get connectionState() { - let connectionStateString = msg.wsNotExist; - if (this.#ws !== null) { - let connectionState = this.#ws.readyState; - switch (connectionState) { - case 0: - connectionStateString = msg.wsConnecting; - break; - case 1: - connectionStateString = msg.wsOpen; - break; - case 2: - connectionStateString = msg.wsClosing; - break; - case 3: - connectionStateString = msg.wsClosed; - break; - default: - connectionStateString = msg.wsNotExist; - } - } - return connectionStateString; - } - - /* - pubSubState method returns the actual state of pubSubState websocket connection - */ - get pubSubState() { - let connectionStateString = msg.wsPubSubNotExist; - if (this.#wsPubSub !== null) { - let connectionState = this.#wsPubSub.readyState; - switch (connectionState) { - case 0: - connectionStateString = msg.wsPubSubConnecting; - break; - case 1: - connectionStateString = msg.wsPubSubOpen; - break; - case 2: - connectionStateString = msg.wsPubSubClosing; - break; - case 3: - connectionStateString = msg.wsPubSubClosed; - break; - default: - connectionStateString = msg.wsPubSubNotExist; - } - } - return connectionStateString; - } - - /* - requestsStackState method return the lits of pending requests - */ - get requestsStackState() { - return this.#requestsStack; - } - - /* - subscriptionsStackState method return the lits of active subscriptions - */ - get subscriptionsStackState() { - return this.#subscriptionsStack; - } - - - /* - exec method calls the private method promiseSend building the object - */ - async exec(command) { - if (this.#ws !== null) { - try { - const response = await this.#promiseSend( - { - id: this.#makeid(5), - type: "exec", - command: command - } - ); - return (response); - } catch (error) { - return { - status: "error", - data: error - }; - } - } else { - return { - status: "error", - data: { - message: msg.wsExecErrorNoConnection - } - } - } - } - - - /* - notify method calls the private method promiseSend building the object - */ - async notify(channel, payload) { - if (this.#ws !== null) { - try { - const response = await this.#promiseSend( - { - id: this.#makeid(5), - type: "notify", - channel: channel, - payload: JSON.stringify(payload) - } - ); - return (response); - } catch (error) { - return { - status: "error", - data: error - }; - } - } else { - return ( - { - status: "error", - data: { - message: msg.wsNotifyErrorNoConnection - } - } - ); - } - } - - - /* - listenChannel method calls the private method #pubsub to register to a new channel - */ - async listenChannel(channel, callback) { - if (this.#ws !== null) { - try { - const response = await this.#pubsub("listenChannel", channel, callback); - return response; - } catch (error) { - return ({ - status: "error", - data: error - }); - } - } else { - return ({ - status: "error", - message: msg.wsListenError.errorNoConnection - }) - } - } - - - /* - listenTable method calls the private method #pubsub to register to a new table - */ - async listenTable(table, callback) { - if (this.#ws !== null) { - try { - const response = await this.#pubsub("listenTable", table, callback); - return response; - } catch (error) { - return ({ - status: "error", - data: error - }); - } - } else { - return ({ - status: "error", - message: msg.wsListenError.errorNoConnection - }) - } - } - - - - /* - unlistenChannel method calls the private method #pubsub to unregister to a channel - */ - async unlistenChannel(channel) { - if (!this.#subscriptionsStack.has(channel)) { - return ( - { - status: "error", - data: { - message: msg.wsUnlistenError.missingSubscritption + " " + channel - } - } - ) - } - - if (this.#ws !== null) { - try { - const response = await this.#pubsub("unlistenChannel", channel, null); - return (response); - } catch (error) { - return { - status: "error", - data: error - }; - } - } else { - return ( - { - status: "error", - data: { - message: msg.wsUnlistenError.errorNoConnection - } - } - ) - } - } - - - /* - unlistenTable method calls the private method #pubsub to unregister to a table - */ - async unlistenTable(table) { - if (!this.#subscriptionsStack.has(table)) { - return ( - { - status: "error", - data: { - message: msg.wsUnlistenError.missingSubscritption + " " + table - } - } - ) - } - - if (this.#ws !== null) { - try { - const response = await this.#pubsub("unlistenTable", table, null); - return (response); - } catch (error) { - return { - status: "error", - data: error - }; - } - } else { - return ( - { - status: "error", - data: { - message: msg.wsUnlistenError.errorNoConnection - } - } - ) - } - } - - /* - listChannels method calls exec method to receive the list of all the existing channels - */ - async listChannels() { - try { - const response = await this.exec("LIST CHANNELS"); - return (response); - } catch (error) { - return { - status: "error", - data: error - }; - } - } - - /* - createChannel method calls exec method to create a new channel - channelName: mandatory, the name of the channel to be created - ifNotExist: optional, if true set in the command the string [IF NOT EXISTS] - */ - async createChannel(channelName, ifNotExist = true) { - try { - //params validation - //check channelName has been provided - if (!channelName) { - return ( - { - status: "error", - data: { - message: msg.createChannelErr.mandatory - } - } - ) - } - if (typeof channelName !== "string") { - return ( - { - status: "error", - data: { - message: msg.createChannelErr.string - } - } - ) - } - //params are ok - let command = `CREATE CHANNEL '${channelName}'`; - command = ifNotExist ? command + " IF NOT EXISTS" : command; - const response = await this.exec(command); - return (response); - } catch (error) { - return { - status: "error", - data: error - }; - } - } - - /* - removeChannel method calls exec method to remove an existing channel - */ - async removeChannel(channelName) { - try { - //params validation - //check channelName has been provided - if (!channelName) { - return ( - { - status: "error", - data: { - message: msg.removeChannelErr.mandatory - } - } - ) - } - if (typeof channelName !== "string") { - return ( - { - status: "error", - data: { - message: msg.removeChannelErr.string - } - } - ) - } - //params are ok - let command = `REMOVE CHANNEL '${channelName}'`; - const response = await this.exec(command); - return (response); - } catch (error) { - return { - status: "error", - data: error - }; - } - } - - - /* PRIVATE METHODS */ - - /* - makeid method generates unique IDs to use for requests - */ - #makeid(length = 5) { - var result = ''; - var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - var charactersLength = characters.length; - for (var i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * - charactersLength)); - } - return result; - } - - /* - connectWs private method is called to create the websocket conenction used to send command request, receive command request response, pubSub subscription request, - */ - #connectWs(url, errorMessage) { - return new Promise((resolve, reject) => { - var ws = new WebSocket(url); - ws.onopen = function () { - resolve(ws); - }; - ws.onerror = function (err) { - reject({ - err: err, - message: errorMessage - }); - }; - }); - } - - /* - onCloseWs private method is called when close event is fired by websocket. - if user provided a callback, this is invoked - */ - #onCloseWs = (event) => { - if (this.onClose !== null) { - this.onClose(msg.wsCloseComplete); - this.#ws.removeEventListener('close', this.#onCloseWs); - this.#ws.removeEventListener('error', this.#onErrorWs); - this.#ws = null; - } - } - - /* - onErrorWs private method is called when error event is fired by websocket. - if user provided a callback, this is invoked - */ - #onErrorWs = (event) => { - if (this.onError !== null) { - this.onError(event, msg.wsOnError); - } - } - - /* - onCloseWsPubSub private method is called when close event is fired by websocket. - if user provided a callback, this is invoked - */ - #onCloseWsPubSub = (event) => { - if (this.onClose !== null) { - this.onClose(msg.wsPubSubCloseComplete); - this.#wsPubSub.removeEventListener('close', this.#onCloseWsPubSub); - this.#wsPubSub.removeEventListener('error', this.#onErrorWsPubSub); - this.#uuid = null; - this.#wsPubSub = null; - } - } - - /* - onError private method is called when error event is fired by websocket. - if user provided a callback, this is invoked - */ - #onErrorWsPubSub = (event) => { - if (this.onError !== null) { - this.onError(event, msg.wsPubSubOnError); - } - } - - /* - pubsub method calls - */ - async #pubsub(type, channel, callback) { - //based on the value of of callback, create a new subscription or remove the subscription - if (callback !== null) { - //check if the channel subscription is already active - if (!this.#subscriptionsStack.has(channel)) { - //if the subscription does not exist - try { - let body; - //based on type value build the right body - //important in case of channel, the channel key is present in the body - //important in case of table, the table key is present in the body - if (type == "listenChannel") { - body = { - id: this.#makeid(5), - type: "listen", - channel: channel.toLowerCase(), - } - } - if (type == "unlistenChannel") { - body = { - id: this.#makeid(5), - type: "unlisten", - channel: channel.toLowerCase(), - } - } - if (type == "listenTable") { - body = { - id: this.#makeid(5), - type: "listen", - table: channel.toLowerCase(), - } - } - if (type == "unlistenTable") { - body = { - id: this.#makeid(5), - type: "unlisten", - table: channel.toLowerCase(), - } - } - const response = await this.#promiseSend(body); - //if this is the first subscription, create the websocket connection dedicated to receive pubSub messages - if (this.#subscriptionsStack.size == 0 && response.status == "success") { - //response here we have authentication information - const uuid = response.data.uuid; - const secret = response.data.secret; - try { - this.#wsPubSubUrl = `wss://web1.sqlitecloud.io:8443/api/v1/wspsub?uuid=${uuid}&secret=${secret}`; - this.#wsPubSub = await this.#connectWs(this.#wsPubSubUrl, "PubSub connection not established"); - //when the PubSub WebSocket is created the channel is added to the stack - this.#subscriptionsStack.set(channel.toLowerCase(), - { - channel: channel.toLowerCase(), - callback: callback - } - ); - this.#uuid = uuid; - //register the onmessage event on pubSub websocket - this.#wsPubSub.addEventListener('message', this.#wsPubSubonMessage); - //register the close event on websocket - this.#wsPubSub.addEventListener('error', this.#onErrorWsPubSub); - //register the close event on websocket - this.#wsPubSub.addEventListener('close', this.#onCloseWsPubSub); - } catch (error) { - return { - status: "error", - data: error - }; - } - } - //if this isn't the first subscription, just add the supscription to the stack - if (this.#subscriptionsStack.size > 0 && response.status == "success") { - this.#subscriptionsStack.set(channel.toLowerCase(), - { - channel: channel.toLowerCase(), - callback: callback - } - ); - } - //build the object returned to client - let userResponse = {}; - userResponse.status = response.status; - if (response.status == "success") { - userResponse.data = {}; - userResponse.data.channel = response.data.channel; - } - if (response.status == "error") { - userResponse.data = response.data; - } - return userResponse; - } catch (error) { - return { - status: "error", - data: error - }; - } - } else { - //if the subscription exists - return ( - { - status: "warning", - data: { - message: msg.wsListenError.alreadySubscribed + " " + channel - } - } - ) - } - } else { - try { - const response = await this.#promiseSend( - { - id: this.#makeid(5), - type: type, - channel: channel.toLowerCase(), - } - ); - this.#subscriptionsStack.delete(channel) - //check the remaing active subscription. If zero the websocket connection used for pubSub can be closed - if (this.#subscriptionsStack.size == 0) { - this.#wsPubSub.removeEventListener('message', this.#wsPubSubonMessage); - this.#wsPubSub.close(1000, msg.wsPubSubCloseByClient); - this.#wsPubSub = null; - } - return (response); - } catch (error) { - return { - status: "error", - data: error - }; - } - } - } - - /* - wsPubSubonMessage private method is called when ad onmessage event is fired on pubSub websocket. - based on the channel indicated in the message the right callback is called - */ - #wsPubSubonMessage = (event) => { - const pubSubMessage = JSON.parse(event.data); - - //since payload can be both a string or JSON, this function based on check of it is or not a valid JSON return the correct parsed paylod - function payloadParser(str) { - try { - JSON.parse(str); - } catch (e) { - return str; - } - return JSON.parse(str); - } - - //build the obj returned to the user removing fields not usefull - const userPubSubMessage = { - channel: pubSubMessage.channel, - sender: pubSubMessage.sender, - payload: payloadParser(pubSubMessage.payload), - ownMessage: this.#uuid == pubSubMessage.sender - } - //this is the case in which the user decide to filter the message sent by himself - if (this.filterSentMessages && this.#uuid == pubSubMessage.sender) { - - } else { - this.#subscriptionsStack.get(pubSubMessage.channel).callback(userPubSubMessage); - } - } - - /* - promiseSend private method send request to the server creating a Promise that resolve when a websocket onmessage event is fired. - */ - #promiseSend(request) { - //request is sent to the server - this.#ws.send(JSON.stringify(request)); - //extract request id - const requestId = request.id; - //define the Promise that wait for the server response - return new Promise((resolve, reject) => { - //define what to do if an answer does not arrive within the set time - const onRequestTimeout = setTimeout(() => { this.#handlePromiseRejectTimeout(requestId) }, this.requestTimeout); - //add the new request to the request stack - this.#requestsStack.set( - requestId, - { - id: requestId, - onRequestTimeout: onRequestTimeout, - resolve: resolve, - reject: reject - } - ) - //if this is the only one request in the stack, register the function to be executed at the onmessage event - if (this.#requestsStack.size == 1) this.#ws.addEventListener('message', this.#handlePromiseResolve); - }) - } - - /* - private handlePromiseResolve method is called when onmessage event is fired. - */ - #handlePromiseResolve = (event) => { - //parse the response sent by the server - const response = JSON.parse(event.data); - //search in the requests stack the request with the same id of the response received by the server. - //it is possible that the request no longer exists as it may have timed out and therefore deleted from the stack. - //if the request was found: - // - the Promise corresponding to the request is resolved returning the response received by the server - // - the timeout related to the request is cleared - // - the new requests stack is stored - // - if there are no pending requests remove the websocket onmessage event - if (this.#requestsStack.has(response.id)) { - //build the obj returned to the user removing based on type - let userResponse = {}; - switch (response.type) { - case "exec": - userResponse = { - status: response.status, - data: response.data - }; - break; - case "notify": - userResponse = { - status: response.status, - data: response.data - }; - break; - case "listen": - //in this case the message is passed as is because the uuid and secret field, if present, will be used to create wsPubSub connection - //the messega will be cleaned from this fields in #pubsub method - userResponse = response; - break; - case "unlisten": - userResponse = { - status: response.status, - data: response.data - }; - break; - default: - userResponse = response; - } - - this.#requestsStack.get(response.id).resolve(userResponse); - clearTimeout(this.#requestsStack.get(response.id).onRequestTimeout); - this.#requestsStack.delete(response.id); - if (this.#requestsStack.size == 0) this.#ws.removeEventListener('message', this.#handlePromiseResolve); - } - } - - /* - private handlePromiseRejectTimeout method is called when a request times out. - */ - #handlePromiseRejectTimeout = (requestID) => { - //search in the requests stack the request with the same id of the request that times out. - //a new requests stack is created by removing the request that times out. - //once the request is found: - // - the Promise corresponding to the reject returning an error message - // - the timeout related to the request is cleared - // - the new requests stack is stored - // - if there are no pending requests remove the websocket onmessage event - if (this.#requestsStack.has(requestID)) { - clearTimeout(this.#requestsStack.get(requestID).onRequestTimeout); - this.#requestsStack.get(requestID).reject({ - message: msg.wsTimeoutError + " " + requestID - }); - this.#requestsStack.delete(requestID); - if (this.#requestsStack.size == 0) this.#ws.removeEventListener('message', this.#handlePromiseResolve); - } - } -} - - - - -/***/ }) - -/******/ }); -/************************************************************************/ -/******/ // The module cache -/******/ var __webpack_module_cache__ = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ // Check if module is in cache -/******/ var cachedModule = __webpack_module_cache__[moduleId]; -/******/ if (cachedModule !== undefined) { -/******/ return cachedModule.exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = __webpack_module_cache__[moduleId] = { -/******/ // no module.id needed -/******/ // no module.loaded needed -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/************************************************************************/ -/******/ /* webpack/runtime/define property getters */ -/******/ (() => { -/******/ // define getter functions for harmony exports -/******/ __webpack_require__.d = (exports, definition) => { -/******/ for(var key in definition) { -/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { -/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); -/******/ } -/******/ } -/******/ }; -/******/ })(); -/******/ -/******/ /* webpack/runtime/hasOwnProperty shorthand */ -/******/ (() => { -/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) -/******/ })(); -/******/ -/************************************************************************/ -/******/ -/******/ // startup -/******/ // Load entry module and return exports -/******/ // This entry module used 'module' so it can't be inlined -/******/ var __webpack_exports__ = __webpack_require__(763); -/******/ -/******/ return __webpack_exports__; -/******/ })() -; -}); \ No newline at end of file diff --git a/JS/dist/1.0/sqlitecloud-sdk.min.js b/JS/dist/1.0/sqlitecloud-sdk.min.js deleted file mode 100644 index 6eef4149..00000000 --- a/JS/dist/1.0/sqlitecloud-sdk.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/*! For license information please see sqlitecloud-sdk.min.js.LICENSE.txt */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SQLiteCloud=t():e.SQLiteCloud=t()}(this,(()=>(()=>{var e={763:(e,t,s)=>{e.exports=s(542).default},542:(e,t,s)=>{"use strict";s.d(t,{default:()=>R});const r="webSocket connection is active.",n="webSocket connection has already been created.",a="webSocket connection cannot be created because a pubSub connection is already active.",i="websocket connection not established. Check your internet connection, project ID and API key.",o="closing of the WebSocket and pubSub started.",u="closing of the WebSocket started.",c="webSocket connection has been closed.",l="closing of the pubSub started.",h="pubSub connection has been closed.",b="there is no WebSocket that can be closed.",d="main WebSocket connection closed by client.",S="pubSub WebSocket connection closed by client.",w="websocket connection not exist.",m="websocket connection error.",p="pubSub connection error.",y="CONNECTING",g="OPEN",k="CLOSING",f="CLOSED",C="pubSub connection not exist.",P="CONNECTING",v="OPEN",E="CLOSING",N="CLOSED",L="you need to create a WebSocket connection. Use the connect method.",q="you need to create a WebSocket connection. Use the connect method.",W={alreadySubscribed:"registration already made to the channel:",errorNoConnection:"you need to create a WebSocket connection. Use the connect method."},T={missingSubscritption:"it is not possible to unlisten unregistered channel:",errorNoConnection:"you have closed the WebSocket connection, it is no longer possible to unlisten channels."},O="the request timed out. ID:",x={mandatory:"channelName is mandatory",string:"channelName has to be a string"},M={mandatory:"channelName is mandatory",string:"channelName has to be a string"};class R{#e=null;#t=null;#s=null;#r=null;#n=new Map;#a=new Map;requestTimeout=3e3;filterSentMessages=!1;constructor(e,t,s=null,r=null){this.url=`wss://web1.sqlitecloud.io:8443/api/v1/${e}/ws?apikey=${t}`,this.onError=s,this.onClose=r}setRequestTimeout(e){this.requestTimeout=e}setFilterSentMessages(e){this.filterSentMessages=e}async connect(){if(null!=this.#e)return{status:"warning",data:{message:n}};if(null!=this.#t)return{status:"warning",data:{message:a}};try{return this.#e=await this.#i(this.url,i),this.#e.addEventListener("error",this.#o),this.#e.addEventListener("close",this.#u),{status:"success",data:{message:r}}}catch(e){return{status:"error",data:e}}}close(e=!0){return e?null!==this.#t&&null!==this.#e?(this.#t.close(1e3,S),this.#a=new Map,this.#e.close(1e3,d),{status:"success",data:{message:o}}):null==this.#t&&null!==this.#e?(this.#e.close(1e3,d),{status:"success",data:{message:u}}):null!=this.#t&&null==this.#e?(this.#a=new Map,this.#t.close(1e3,d),{status:"success",data:{message:l}}):{status:"error",data:{message:b}}:e?void 0:null!==this.#e?(this.#e.close(1e3,d),{status:"success",data:{message:u}}):{status:"error",data:{message:b}}}get connectionState(){let e=w;if(null!==this.#e){switch(this.#e.readyState){case 0:e=y;break;case 1:e=g;break;case 2:e=k;break;case 3:e=f;break;default:e=w}}return e}get pubSubState(){let e=C;if(null!==this.#t){switch(this.#t.readyState){case 0:e=P;break;case 1:e=v;break;case 2:e=E;break;case 3:e=N;break;default:e=C}}return e}get requestsStackState(){return this.#n}get subscriptionsStackState(){return this.#a}async exec(e){if(null===this.#e)return{status:"error",data:{message:L}};try{return await this.#c({id:this.#l(5),type:"exec",command:e})}catch(e){return{status:"error",data:e}}}async notify(e,t){if(null===this.#e)return{status:"error",data:{message:q}};try{return await this.#c({id:this.#l(5),type:"notify",channel:e,payload:JSON.stringify(t)})}catch(e){return{status:"error",data:e}}}async listenChannel(e,t){if(null===this.#e)return{status:"error",message:W.errorNoConnection};try{return await this.#h("listenChannel",e,t)}catch(e){return{status:"error",data:e}}}async listenTable(e,t){if(null===this.#e)return{status:"error",message:W.errorNoConnection};try{return await this.#h("listenTable",e,t)}catch(e){return{status:"error",data:e}}}async unlistenChannel(e){if(!this.#a.has(e))return{status:"error",data:{message:T.missingSubscritption+" "+e}};if(null===this.#e)return{status:"error",data:{message:T.errorNoConnection}};try{return await this.#h("unlistenChannel",e,null)}catch(e){return{status:"error",data:e}}}async unlistenTable(e){if(!this.#a.has(e))return{status:"error",data:{message:T.missingSubscritption+" "+e}};if(null===this.#e)return{status:"error",data:{message:T.errorNoConnection}};try{return await this.#h("unlistenTable",e,null)}catch(e){return{status:"error",data:e}}}async listChannels(){try{return await this.exec("LIST CHANNELS")}catch(e){return{status:"error",data:e}}}async createChannel(e,t=!0){try{if(!e)return{status:"error",data:{message:x.mandatory}};if("string"!=typeof e)return{status:"error",data:{message:x.string}};let s=`CREATE CHANNEL '${e}'`;s=t?s+" IF NOT EXISTS":s;return await this.exec(s)}catch(e){return{status:"error",data:e}}}async removeChannel(e){try{if(!e)return{status:"error",data:{message:M.mandatory}};if("string"!=typeof e)return{status:"error",data:{message:M.string}};let t=`REMOVE CHANNEL '${e}'`;return await this.exec(t)}catch(e){return{status:"error",data:e}}}#l(e=5){for(var t="",s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",r=s.length,n=0;n{var n=new WebSocket(e);n.onopen=function(){s(n)},n.onerror=function(e){r({err:e,message:t})}}))}#u=e=>{null!==this.onClose&&(this.onClose(c),this.#e.removeEventListener("close",this.#u),this.#e.removeEventListener("error",this.#o),this.#e=null)};#o=e=>{null!==this.onError&&this.onError(e,m)};#b=e=>{null!==this.onClose&&(this.onClose(h),this.#t.removeEventListener("close",this.#b),this.#t.removeEventListener("error",this.#d),this.#r=null,this.#t=null)};#d=e=>{null!==this.onError&&this.onError(e,p)};async#h(e,t,s){if(null!==s){if(this.#a.has(t))return{status:"warning",data:{message:W.alreadySubscribed+" "+t}};try{let r;"listenChannel"==e&&(r={id:this.#l(5),type:"listen",channel:t.toLowerCase()}),"unlistenChannel"==e&&(r={id:this.#l(5),type:"unlisten",channel:t.toLowerCase()}),"listenTable"==e&&(r={id:this.#l(5),type:"listen",table:t.toLowerCase()}),"unlistenTable"==e&&(r={id:this.#l(5),type:"unlisten",table:t.toLowerCase()});const n=await this.#c(r);if(0==this.#a.size&&"success"==n.status){const e=n.data.uuid,r=n.data.secret;try{this.#s=`wss://web1.sqlitecloud.io:8443/api/v1/wspsub?uuid=${e}&secret=${r}`,this.#t=await this.#i(this.#s,"PubSub connection not established"),this.#a.set(t.toLowerCase(),{channel:t.toLowerCase(),callback:s}),this.#r=e,this.#t.addEventListener("message",this.#S),this.#t.addEventListener("error",this.#d),this.#t.addEventListener("close",this.#b)}catch(e){return{status:"error",data:e}}}this.#a.size>0&&"success"==n.status&&this.#a.set(t.toLowerCase(),{channel:t.toLowerCase(),callback:s});let a={};return a.status=n.status,"success"==n.status&&(a.data={},a.data.channel=n.data.channel),"error"==n.status&&(a.data=n.data),a}catch(e){return{status:"error",data:e}}}else try{const s=await this.#c({id:this.#l(5),type:e,channel:t.toLowerCase()});return this.#a.delete(t),0==this.#a.size&&(this.#t.removeEventListener("message",this.#S),this.#t.close(1e3,S),this.#t=null),s}catch(e){return{status:"error",data:e}}}#S=e=>{const t=JSON.parse(e.data);const s={channel:t.channel,sender:t.sender,payload:function(e){try{JSON.parse(e)}catch(t){return e}return JSON.parse(e)}(t.payload),ownMessage:this.#r==t.sender};this.filterSentMessages&&this.#r==t.sender||this.#a.get(t.channel).callback(s)};#c(e){this.#e.send(JSON.stringify(e));const t=e.id;return new Promise(((e,s)=>{const r=setTimeout((()=>{this.#w(t)}),this.requestTimeout);this.#n.set(t,{id:t,onRequestTimeout:r,resolve:e,reject:s}),1==this.#n.size&&this.#e.addEventListener("message",this.#m)}))}#m=e=>{const t=JSON.parse(e.data);if(this.#n.has(t.id)){let e={};switch(t.type){case"exec":case"notify":case"unlisten":e={status:t.status,data:t.data};break;default:e=t}this.#n.get(t.id).resolve(e),clearTimeout(this.#n.get(t.id).onRequestTimeout),this.#n.delete(t.id),0==this.#n.size&&this.#e.removeEventListener("message",this.#m)}};#w=e=>{this.#n.has(e)&&(clearTimeout(this.#n.get(e).onRequestTimeout),this.#n.get(e).reject({message:O+" "+e}),this.#n.delete(e),0==this.#n.size&&this.#e.removeEventListener("message",this.#m))}}}},t={};function s(r){var n=t[r];if(void 0!==n)return n.exports;var a=t[r]={exports:{}};return e[r](a,a.exports,s),a.exports}return s.d=(e,t)=>{for(var r in t)s.o(t,r)&&!s.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},s.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),s(763)})())); -//# sourceMappingURL=sqlitecloud-sdk.min.js.map \ No newline at end of file diff --git a/JS/dist/1.0/sqlitecloud-sdk.min.js.LICENSE.txt b/JS/dist/1.0/sqlitecloud-sdk.min.js.LICENSE.txt deleted file mode 100644 index f8f122f6..00000000 --- a/JS/dist/1.0/sqlitecloud-sdk.min.js.LICENSE.txt +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * SQLite Cloud SDK v1.0.6 - * https://sqlitecloud.io/ - * - * Copyright 2023, SQLite Cloud - * Released under the MIT licence. - */ diff --git a/JS/dist/1.0/sqlitecloud-sdk.min.js.map b/JS/dist/1.0/sqlitecloud-sdk.min.js.map deleted file mode 100644 index efc64cd7..00000000 --- a/JS/dist/1.0/sqlitecloud-sdk.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"sqlitecloud-sdk.min.js","mappings":";CAAA,SAA2CA,EAAMC,GAC1B,iBAAZC,SAA0C,iBAAXC,OACxCA,OAAOD,QAAUD,IACQ,mBAAXG,QAAyBA,OAAOC,IAC9CD,OAAO,GAAIH,GACe,iBAAZC,QACdA,QAAqB,YAAID,IAEzBD,EAAkB,YAAIC,GACvB,CATD,CASGK,MAAM,IACT,2BCVAH,EAAOD,QAAU,EAAjB,gECAO,MAAMK,EACE,kCADFA,EAES,iDAFTA,EAGmB,wFAHnBA,EAIK,gGAJLA,EAKsB,+CALtBA,EAMO,oCANPA,EAQM,wCARNA,EASa,iCATbA,EAUY,qCAVZA,EAWK,4CAXLA,EAYM,8CAZNA,EAaY,gDAbZA,EAcC,kCAdDA,EAeA,8BAfAA,EAgBM,2BAhBNA,EAiBG,aAjBHA,EAkBH,OAlBGA,EAmBA,UAnBAA,EAoBD,SApBCA,EAqBO,+BArBPA,EAsBS,aAtBTA,EAuBG,OAvBHA,EAwBM,UAxBNA,EAyBK,SAzBLA,EA0Bc,qEA1BdA,EA2BgB,qEA3BhBA,EA4BI,CACbC,kBAAmB,4CACnBC,kBAAmB,sEA9BVF,EAgCM,CACfG,qBAAsB,uDACtBD,kBAAmB,4FAlCVF,EAoCK,6BApCLA,EAqCM,CACfI,UAAW,2BACXC,OAAQ,kCAvCCL,EAyCM,CACfI,UAAW,2BACXC,OAAQ,kCCvCG,MAAMC,EAWnB,GAAM,KAQN,GAAY,KACZ,GAAe,KACf,GAAQ,KAYR,GAAiB,IAAIC,IAUrB,GAAsB,IAAIA,IAO1BC,eAAiB,IACjBC,oBAAqB,EAUrBC,YAAYC,EAAWC,EAAQC,EAAU,KAAMC,EAAU,MACvDf,KAAKgB,IAAM,yCAAyCJ,eAAuBC,IAC3Eb,KAAKc,QAAUA,EACfd,KAAKe,QAAUA,CACjB,CAMAE,kBAAkBC,GAChBlB,KAAKS,eAAiBS,CACxB,CAKAC,sBAAsBD,GACpBlB,KAAKU,mBAAqBQ,CAC5B,CAMAE,gBACE,GAAgB,MAAZpB,MAAK,EA6BP,MAAO,CACLqB,OAAQ,UACRC,KAAM,CACJC,QAAStB,IA/Bb,GAAsB,MAAlBD,MAAK,EAoBP,MAAO,CACLqB,OAAQ,UACRC,KAAM,CACJC,QAAStB,IAtBb,IAME,OALAD,MAAK,QAAYA,MAAK,EAAWA,KAAKgB,IAAKf,GAE3CD,MAAK,EAAIwB,iBAAiB,QAASxB,MAAK,GAExCA,MAAK,EAAIwB,iBAAiB,QAASxB,MAAK,GACjC,CACLqB,OAAQ,UACRC,KAAM,CACJC,QAAStB,GAQf,CALE,MAAOwB,GACP,MAAO,CACLJ,OAAQ,QACRC,KAAMG,EAEV,CAiBN,CAOAC,MAAMC,GAAc,GAClB,OAAIA,EACqB,OAAnB3B,MAAK,GAAmC,OAAbA,MAAK,GAClCA,MAAK,EAAU0B,MAAM,IAAMzB,GAC3BD,MAAK,EAAsB,IAAIQ,IAC/BR,MAAK,EAAI0B,MAAM,IAAMzB,GACd,CAEHoB,OAAQ,UACRC,KAAM,CACJC,QAAStB,KAIY,MAAlBD,MAAK,GAAkC,OAAbA,MAAK,GACxCA,MAAK,EAAI0B,MAAM,IAAMzB,GACd,CAEHoB,OAAQ,UACRC,KAAM,CACJC,QAAStB,KAIY,MAAlBD,MAAK,GAAiC,MAAZA,MAAK,GACxCA,MAAK,EAAsB,IAAIQ,IAC/BR,MAAK,EAAU0B,MAAM,IAAMzB,GACpB,CAEHoB,OAAQ,UACRC,KAAM,CACJC,QAAStB,KAKR,CAEHoB,OAAQ,QACRC,KAAM,CACJC,QAAStB,IAMd0B,OAAL,EACmB,OAAb3B,MAAK,GACPA,MAAK,EAAI0B,MAAM,IAAMzB,GACd,CAEHoB,OAAQ,UACRC,KAAM,CACJC,QAAStB,KAKR,CAEHoB,OAAQ,QACRC,KAAM,CACJC,QAAStB,GAMrB,CAKI2B,sBACF,IAAIC,EAAwB5B,EAC5B,GAAiB,OAAbD,MAAK,EAAc,CAErB,OADsBA,MAAK,EAAI8B,YAE7B,KAAK,EACHD,EAAwB5B,EACxB,MACF,KAAK,EACH4B,EAAwB5B,EACxB,MACF,KAAK,EACH4B,EAAwB5B,EACxB,MACF,KAAK,EACH4B,EAAwB5B,EACxB,MACF,QACE4B,EAAwB5B,EAE9B,CACA,OAAO4B,CACT,CAKIE,kBACF,IAAIF,EAAwB5B,EAC5B,GAAuB,OAAnBD,MAAK,EAAoB,CAE3B,OADsBA,MAAK,EAAU8B,YAEnC,KAAK,EACHD,EAAwB5B,EACxB,MACF,KAAK,EACH4B,EAAwB5B,EACxB,MACF,KAAK,EACH4B,EAAwB5B,EACxB,MACF,KAAK,EACH4B,EAAwB5B,EACxB,MACF,QACE4B,EAAwB5B,EAE9B,CACA,OAAO4B,CACT,CAKIG,yBACF,OAAOhC,MAAK,CACd,CAKIiC,8BACF,OAAOjC,MAAK,CACd,CAMAoB,WAAWc,GACT,GAAiB,OAAblC,MAAK,EAiBP,MAAO,CACLqB,OAAQ,QACRC,KAAM,CACJC,QAAStB,IAnBb,IAQE,aAPuBD,MAAK,EAC1B,CACEmC,GAAInC,MAAK,EAAQ,GACjBoC,KAAM,OACNF,QAASA,GASf,CALE,MAAOT,GACP,MAAO,CACLJ,OAAQ,QACRC,KAAMG,EAEV,CASJ,CAMAL,aAAaiB,EAASC,GACpB,GAAiB,OAAbtC,MAAK,EAkBP,MAAO,CAEHqB,OAAQ,QACRC,KAAM,CACJC,QAAStB,IArBf,IASE,aARuBD,MAAK,EAC1B,CACEmC,GAAInC,MAAK,EAAQ,GACjBoC,KAAM,SACNC,QAASA,EACTC,QAASC,KAAKC,UAAUF,IAS9B,CALE,MAAOb,GACP,MAAO,CACLJ,OAAQ,QACRC,KAAMG,EAEV,CAWJ,CAMAL,oBAAoBiB,EAASI,GAC3B,GAAiB,OAAbzC,MAAK,EAWP,MAAO,CACLqB,OAAQ,QACRE,QAAStB,EAAkBE,mBAZ7B,IAEE,aADuBH,MAAK,EAAQ,gBAAiBqC,EAASI,EAOhE,CALE,MAAOhB,GACP,MAAO,CACLJ,OAAQ,QACRC,KAAMG,EAEV,CAOJ,CAMAL,kBAAkBsB,EAAOD,GACvB,GAAiB,OAAbzC,MAAK,EAWP,MAAO,CACLqB,OAAQ,QACRE,QAAStB,EAAkBE,mBAZ7B,IAEE,aADuBH,MAAK,EAAQ,cAAe0C,EAAOD,EAO5D,CALE,MAAOhB,GACP,MAAO,CACLJ,OAAQ,QACRC,KAAMG,EAEV,CAOJ,CAOAL,sBAAsBiB,GACpB,IAAKrC,MAAK,EAAoB2C,IAAIN,GAChC,MAAO,CAEHhB,OAAQ,QACRC,KAAM,CACJC,QAAStB,EAAoBG,qBAAuB,IAAMiC,IAMlE,GAAiB,OAAbrC,MAAK,EAWP,MAAO,CAEHqB,OAAQ,QACRC,KAAM,CACJC,QAAStB,EAAoBE,oBAdnC,IAEE,aADuBH,MAAK,EAAQ,kBAAmBqC,EAAS,KAOlE,CALE,MAAOZ,GACP,MAAO,CACLJ,OAAQ,QACRC,KAAMG,EAEV,CAWJ,CAMAL,oBAAoBsB,GAClB,IAAK1C,MAAK,EAAoB2C,IAAID,GAChC,MAAO,CAEHrB,OAAQ,QACRC,KAAM,CACJC,QAAStB,EAAoBG,qBAAuB,IAAMsC,IAMlE,GAAiB,OAAb1C,MAAK,EAWP,MAAO,CAEHqB,OAAQ,QACRC,KAAM,CACJC,QAAStB,EAAoBE,oBAdnC,IAEE,aADuBH,MAAK,EAAQ,gBAAiB0C,EAAO,KAO9D,CALE,MAAOjB,GACP,MAAO,CACLJ,OAAQ,QACRC,KAAMG,EAEV,CAWJ,CAKAL,qBACE,IAEE,aADuBpB,KAAK4C,KAAK,gBAOnC,CALE,MAAOnB,GACP,MAAO,CACLJ,OAAQ,QACRC,KAAMG,EAEV,CACF,CAOAL,oBAAoByB,EAAaC,GAAa,GAC5C,IAGE,IAAKD,EACH,MAAO,CAEHxB,OAAQ,QACRC,KAAM,CACJC,QAAStB,EAAqBI,YAKtC,GAA2B,iBAAhBwC,EACT,MAAO,CAEHxB,OAAQ,QACRC,KAAM,CACJC,QAAStB,EAAqBK,SAMtC,IAAI4B,EAAU,mBAAmBW,KACjCX,EAAUY,EAAaZ,EAAU,iBAAmBA,EAEpD,aADuBlC,KAAK4C,KAAKV,EAOnC,CALE,MAAOT,GACP,MAAO,CACLJ,OAAQ,QACRC,KAAMG,EAEV,CACF,CAKAL,oBAAoByB,GAClB,IAGE,IAAKA,EACH,MAAO,CAEHxB,OAAQ,QACRC,KAAM,CACJC,QAAStB,EAAqBI,YAKtC,GAA2B,iBAAhBwC,EACT,MAAO,CAEHxB,OAAQ,QACRC,KAAM,CACJC,QAAStB,EAAqBK,SAMtC,IAAI4B,EAAU,mBAAmBW,KAEjC,aADuB7C,KAAK4C,KAAKV,EAOnC,CALE,MAAOT,GACP,MAAO,CACLJ,OAAQ,QACRC,KAAMG,EAEV,CACF,CAQA,GAAQsB,EAAS,GAIf,IAHA,IAAIC,EAAS,GACTC,EAAa,iEACbC,EAAmBD,EAAWF,OACzBI,EAAI,EAAGA,EAAIJ,EAAQI,IAC1BH,GAAUC,EAAWG,OAAOC,KAAKC,MAAMD,KAAKE,SAC1CL,IAEJ,OAAOF,CACT,CAKA,GAAWhC,EAAKwC,GACd,OAAO,IAAIC,SAAQ,CAACC,EAASC,KAC3B,IAAIC,EAAK,IAAIC,UAAU7C,GACvB4C,EAAGE,OAAS,WACVJ,EAAQE,EACV,EACAA,EAAGG,QAAU,SAAUC,GACrBL,EAAO,CACLK,IAAKA,EACLzC,QAASiC,GAEb,CAAC,GAEL,CAMA,GAAcS,IACS,OAAjBjE,KAAKe,UACPf,KAAKe,QAAQd,GACbD,MAAK,EAAIkE,oBAAoB,QAASlE,MAAK,GAC3CA,MAAK,EAAIkE,oBAAoB,QAASlE,MAAK,GAC3CA,MAAK,EAAM,KACb,EAOF,GAAciE,IACS,OAAjBjE,KAAKc,SACPd,KAAKc,QAAQmD,EAAOhE,EACtB,EAOF,GAAoBgE,IACG,OAAjBjE,KAAKe,UACPf,KAAKe,QAAQd,GACbD,MAAK,EAAUkE,oBAAoB,QAASlE,MAAK,GACjDA,MAAK,EAAUkE,oBAAoB,QAASlE,MAAK,GACjDA,MAAK,EAAQ,KACbA,MAAK,EAAY,KACnB,EAOF,GAAoBiE,IACG,OAAjBjE,KAAKc,SACPd,KAAKc,QAAQmD,EAAOhE,EACtB,EAMFmB,QAAcgB,EAAMC,EAASI,GAE3B,GAAiB,OAAbA,EAAmB,CAErB,GAAKzC,MAAK,EAAoB2C,IAAIN,GA6FhC,MAAO,CAEHhB,OAAQ,UACRC,KAAM,CACJC,QAAStB,EAAkBC,kBAAoB,IAAMmC,IA/F3D,IACE,IAAI8B,EAIQ,iBAAR/B,IACF+B,EAAO,CACLhC,GAAInC,MAAK,EAAQ,GACjBoC,KAAM,SACNC,QAASA,EAAQ+B,gBAGT,mBAARhC,IACF+B,EAAO,CACLhC,GAAInC,MAAK,EAAQ,GACjBoC,KAAM,WACNC,QAASA,EAAQ+B,gBAGT,eAARhC,IACF+B,EAAO,CACLhC,GAAInC,MAAK,EAAQ,GACjBoC,KAAM,SACNM,MAAOL,EAAQ+B,gBAGP,iBAARhC,IACF+B,EAAO,CACLhC,GAAInC,MAAK,EAAQ,GACjBoC,KAAM,WACNM,MAAOL,EAAQ+B,gBAGnB,MAAMC,QAAiBrE,MAAK,EAAamE,GAEzC,GAAqC,GAAjCnE,MAAK,EAAoBsE,MAAgC,WAAnBD,EAAShD,OAAqB,CAEtE,MAAMkD,EAAOF,EAAS/C,KAAKiD,KACrBC,EAASH,EAAS/C,KAAKkD,OAC7B,IACExE,MAAK,EAAe,qDAAqDuE,YAAeC,IACxFxE,MAAK,QAAkBA,MAAK,EAAWA,MAAK,EAAc,qCAE1DA,MAAK,EAAoByE,IAAIpC,EAAQ+B,cACnC,CACE/B,QAASA,EAAQ+B,cACjB3B,SAAUA,IAGdzC,MAAK,EAAQuE,EAEbvE,MAAK,EAAUwB,iBAAiB,UAAWxB,MAAK,GAEhDA,MAAK,EAAUwB,iBAAiB,QAASxB,MAAK,GAE9CA,MAAK,EAAUwB,iBAAiB,QAASxB,MAAK,EAMhD,CALE,MAAOyB,GACP,MAAO,CACLJ,OAAQ,QACRC,KAAMG,EAEV,CACF,CAEIzB,MAAK,EAAoBsE,KAAO,GAAwB,WAAnBD,EAAShD,QAChDrB,MAAK,EAAoByE,IAAIpC,EAAQ+B,cACnC,CACE/B,QAASA,EAAQ+B,cACjB3B,SAAUA,IAKhB,IAAIiC,EAAe,CAAC,EASpB,OARAA,EAAarD,OAASgD,EAAShD,OACR,WAAnBgD,EAAShD,SACXqD,EAAapD,KAAO,CAAC,EACrBoD,EAAapD,KAAKe,QAAUgC,EAAS/C,KAAKe,SAErB,SAAnBgC,EAAShD,SACXqD,EAAapD,KAAO+C,EAAS/C,MAExBoD,CAMT,CALE,MAAOjD,GACP,MAAO,CACLJ,OAAQ,QACRC,KAAMG,EAEV,CAYJ,MACE,IACE,MAAM4C,QAAiBrE,MAAK,EAC1B,CACEmC,GAAInC,MAAK,EAAQ,GACjBoC,KAAMA,EACNC,QAASA,EAAQ+B,gBAUrB,OAPApE,MAAK,EAAoB2E,OAAOtC,GAEK,GAAjCrC,MAAK,EAAoBsE,OAC3BtE,MAAK,EAAUkE,oBAAoB,UAAWlE,MAAK,GACnDA,MAAK,EAAU0B,MAAM,IAAMzB,GAC3BD,MAAK,EAAY,MAEZ,CAMT,CALE,MAAOyB,GACP,MAAO,CACLJ,OAAQ,QACRC,KAAMG,EAEV,CAEJ,CAMA,GAAsBwC,IACpB,MAAMW,EAAgBrC,KAAKsC,MAAMZ,EAAM3C,MAavC,MAAMwD,EAAoB,CACxBzC,QAASuC,EAAcvC,QACvB0C,OAAQH,EAAcG,OACtBzC,QAbF,SAAuB0C,GACrB,IACEzC,KAAKsC,MAAMG,EAGb,CAFE,MAAOC,GACP,OAAOD,CACT,CACA,OAAOzC,KAAKsC,MAAMG,EACpB,CAMWE,CAAcN,EAActC,SACrC6C,WAAYnF,MAAK,GAAS4E,EAAcG,QAGtC/E,KAAKU,oBAAsBV,MAAK,GAAS4E,EAAcG,QAGzD/E,MAAK,EAAoBoF,IAAIR,EAAcvC,SAASI,SAASqC,EAC/D,EAMF,GAAaO,GAEXrF,MAAK,EAAIsF,KAAK/C,KAAKC,UAAU6C,IAE7B,MAAME,EAAYF,EAAQlD,GAE1B,OAAO,IAAIsB,SAAQ,CAACC,EAASC,KAE3B,MAAM6B,EAAmBC,YAAW,KAAQzF,MAAK,EAA4BuF,EAAS,GAAKvF,KAAKS,gBAEhGT,MAAK,EAAeyE,IAClBc,EACA,CACEpD,GAAIoD,EACJC,iBAAkBA,EAClB9B,QAASA,EACTC,OAAQA,IAIoB,GAA5B3D,MAAK,EAAesE,MAAWtE,MAAK,EAAIwB,iBAAiB,UAAWxB,MAAK,EAAsB,GAEvG,CAKA,GAAyBiE,IAEvB,MAAMI,EAAW9B,KAAKsC,MAAMZ,EAAM3C,MAQlC,GAAItB,MAAK,EAAe2C,IAAI0B,EAASlC,IAAK,CAExC,IAAIuC,EAAe,CAAC,EACpB,OAAQL,EAASjC,MACf,IAAK,OAML,IAAK,SAWL,IAAK,WACHsC,EAAe,CACbrD,OAAQgD,EAAShD,OACjBC,KAAM+C,EAAS/C,MAEjB,MACF,QACEoD,EAAeL,EAGnBrE,MAAK,EAAeoF,IAAIf,EAASlC,IAAIuB,QAAQgB,GAC7CgB,aAAa1F,MAAK,EAAeoF,IAAIf,EAASlC,IAAIqD,kBAClDxF,MAAK,EAAe2E,OAAON,EAASlC,IACJ,GAA5BnC,MAAK,EAAesE,MAAWtE,MAAK,EAAIkE,oBAAoB,UAAWlE,MAAK,EAClF,GAMF,GAA+B2F,IAQzB3F,MAAK,EAAe2C,IAAIgD,KAC1BD,aAAa1F,MAAK,EAAeoF,IAAIO,GAAWH,kBAChDxF,MAAK,EAAeoF,IAAIO,GAAWhC,OAAO,CACxCpC,QAAStB,EAAqB,IAAM0F,IAEtC3F,MAAK,EAAe2E,OAAOgB,GACK,GAA5B3F,MAAK,EAAesE,MAAWtE,MAAK,EAAIkE,oBAAoB,UAAWlE,MAAK,GAClF,KCp4BA4F,EAA2B,CAAC,EAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBE,IAAjBD,EACH,OAAOA,EAAanG,QAGrB,IAAIC,EAAS+F,EAAyBE,GAAY,CAGjDlG,QAAS,CAAC,GAOX,OAHAqG,EAAoBH,GAAUjG,EAAQA,EAAOD,QAASiG,GAG/ChG,EAAOD,OACf,QCrBAiG,EAAoBK,EAAI,CAACtG,EAASuG,KACjC,IAAI,IAAIC,KAAOD,EACXN,EAAoBQ,EAAEF,EAAYC,KAASP,EAAoBQ,EAAEzG,EAASwG,IAC5EE,OAAOC,eAAe3G,EAASwG,EAAK,CAAEI,YAAY,EAAMpB,IAAKe,EAAWC,IAE1E,ECNDP,EAAoBQ,EAAI,CAACI,EAAKC,IAAUJ,OAAOK,UAAUC,eAAeC,KAAKJ,EAAKC,GCGxDb,EAAoB,MPO9C","sources":["webpack://SQLiteCloud/webpack/universalModuleDefinition","webpack://SQLiteCloud/./src/core/index.js","webpack://SQLiteCloud/./src/core/messages.js","webpack://SQLiteCloud/./src/core/sqlitecloud.js","webpack://SQLiteCloud/webpack/bootstrap","webpack://SQLiteCloud/webpack/runtime/define property getters","webpack://SQLiteCloud/webpack/runtime/hasOwnProperty shorthand","webpack://SQLiteCloud/webpack/startup"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"SQLiteCloud\"] = factory();\n\telse\n\t\troot[\"SQLiteCloud\"] = factory();\n})(this, () => {\nreturn ","module.exports = require('./sqlitecloud.js').default;\r\n","export const msg = {\r\n wsConnectOk: \"webSocket connection is active.\",\r\n wsAlreadyConnected: \"webSocket connection has already been created.\",\r\n wsCantConnectedWsPubSubExist: \"webSocket connection cannot be created because a pubSub connection is already active.\",\r\n wsConnectError: \"websocket connection not established. Check your internet connection, project ID and API key.\",\r\n wsClosingWsPubSubClosingProcess: \"closing of the WebSocket and pubSub started.\",\r\n wsClosingProcess: \"closing of the WebSocket started.\",\r\n wsPubSubClosingProcess: \"closing of the pubSub started.\",\r\n wsCloseComplete: \"webSocket connection has been closed.\",\r\n wsPubSubClosingProcess: \"closing of the pubSub started.\",\r\n wsPubSubCloseComplete: \"pubSub connection has been closed.\",\r\n wsClosingError: \"there is no WebSocket that can be closed.\",\r\n wsCloseByClient: \"main WebSocket connection closed by client.\",\r\n wsPubSubCloseByClient: \"pubSub WebSocket connection closed by client.\",\r\n wsNotExist: \"websocket connection not exist.\",\r\n wsOnError: \"websocket connection error.\",\r\n wsPubSubOnError: \"pubSub connection error.\",\r\n wsConnecting: \"CONNECTING\",\r\n wsOpen: \"OPEN\",\r\n wsClosing: \"CLOSING\",\r\n wsClosed: \"CLOSED\",\r\n wsPubSubNotExist: \"pubSub connection not exist.\",\r\n wsPubSubConnecting: \"CONNECTING\",\r\n wsPubSubOpen: \"OPEN\",\r\n wsPubSubClosing: \"CLOSING\",\r\n wsPubSubClosed: \"CLOSED\",\r\n wsExecErrorNoConnection: \"you need to create a WebSocket connection. Use the connect method.\",\r\n wsNotifyErrorNoConnection: \"you need to create a WebSocket connection. Use the connect method.\",\r\n wsListenError: {\r\n alreadySubscribed: \"registration already made to the channel:\",\r\n errorNoConnection: \"you need to create a WebSocket connection. Use the connect method.\",\r\n },\r\n wsUnlistenError: {\r\n missingSubscritption: \"it is not possible to unlisten unregistered channel:\",\r\n errorNoConnection: \"you have closed the WebSocket connection, it is no longer possible to unlisten channels.\",\r\n },\r\n wsTimeoutError: \"the request timed out. ID:\",\r\n createChannelErr:{\r\n mandatory: \"channelName is mandatory\",\r\n string: \"channelName has to be a string\"\r\n },\r\n removeChannelErr:{\r\n mandatory: \"channelName is mandatory\",\r\n string: \"channelName has to be a string\"\r\n } \r\n}","import { msg } from \"./messages\";\r\n\r\n\r\n\r\nexport default class SQLiteCloud {\r\n /* PRIVATE PROPERTIES */\r\n\r\n /*\r\n #ws private property stores the websocket used:\r\n - to send \"exec\" type request\r\n - to send \"pubsub\" subscription request\r\n - to receive response to \"exec\" type request\r\n \r\n User receives the responses to his requests reading the result of a Promise.\r\n */\r\n #ws = null;\r\n\r\n /*\r\n #wsPubSub private property stores the websocket used to receive pubSub messages.\r\n #wsPubSubUrl private property stores the websocket url\r\n #uuid private property stores the user identifier. This can be used to not received messages sent by the current user\r\n When a new message is received, based on the channel, is selected the callbacks to be called cycling through subscriptionsStack property\r\n */\r\n #wsPubSub = null;\r\n #wsPubSubUrl = null;\r\n #uuid = null;\r\n\r\n /*\r\n #requestsStack private property stores the list of pending requests, in this way the user can send multiple parallel requests.\r\n For each request an object that contains the following is stored:\r\n {\r\n id: //unique id associated with the request \r\n onRequestTimeout: //function called when the request times out\r\n resolve: resolve, //function called when the Promise resolve\r\n reject: reject //function called when the Promise reject\r\n }\r\n */\r\n #requestsStack = new Map();\r\n\r\n /*\r\n #subscriptionsStack private property stores the list of pubSub subscriptions.\r\n For each subscription an object that contains the following is stored:\r\n {\r\n channel: //the name of the channel you are subscribed to \r\n callback: //the function called when a new message arrives\r\n }\r\n */\r\n #subscriptionsStack = new Map();\r\n\r\n /* PUBLIC PROPERTIES */\r\n /*\r\n requestTimeout property stores the time available to receive a response before the request times out.\r\n filterSentMessages the library not return messages sent by the user\r\n */\r\n requestTimeout = 3000;\r\n filterSentMessages = false;\r\n\r\n\r\n /* CONSTRUCTOR */\r\n /*\r\n SQLiteCloud class constructor receives:\r\n - project ID (required)\r\n - api key (required)\r\n - webSocket callback event (optional)\r\n */\r\n constructor(projectID, apikey, onError = null, onClose = null) {\r\n this.url = `wss://web1.sqlitecloud.io:8443/api/v1/${projectID}/ws?apikey=${apikey}`;\r\n this.onError = onError;\r\n this.onClose = onClose;\r\n }\r\n\r\n /* PUBLIC METHODS */\r\n /*\r\n setRequestTimeout method allows the user to change the request timeout value\r\n */\r\n setRequestTimeout(value) {\r\n this.requestTimeout = value;\r\n }\r\n\r\n /*\r\n setFilterSentMessages method allows the user to filter or not sent messagess\r\n */\r\n setFilterSentMessages(value) {\r\n this.filterSentMessages = value;\r\n }\r\n\r\n\r\n /*\r\n connect method opens websocket connection\r\n */\r\n async connect() {\r\n if (this.#ws == null) {\r\n if (this.#wsPubSub == null) {\r\n try {\r\n this.#ws = await this.#connectWs(this.url, msg.wsConnectError);\r\n //register the error event on websocket\r\n this.#ws.addEventListener('error', this.#onErrorWs);\r\n //register the close event on websocket\r\n this.#ws.addEventListener('close', this.#onCloseWs);\r\n return {\r\n status: \"success\",\r\n data: {\r\n message: msg.wsConnectOk\r\n }\r\n }\r\n } catch (error) {\r\n return {\r\n status: \"error\",\r\n data: error\r\n };\r\n }\r\n } else {\r\n return {\r\n status: \"warning\",\r\n data: {\r\n message: msg.wsCantConnectedWsPubSubExist\r\n }\r\n };\r\n }\r\n } else {\r\n return {\r\n status: \"warning\",\r\n data: {\r\n message: msg.wsAlreadyConnected\r\n }\r\n }\r\n }\r\n }\r\n\r\n /*\r\n close method closes websocket connection and if exist pubSub websocket connection\r\n - closePubSub = true (default true), this method close both WebSocket\r\n - closePubSub = false this method close only main WebSocket\r\n */\r\n close(closePubSub = true) {\r\n if (closePubSub) {\r\n if (this.#wsPubSub !== null && this.#ws !== null) {\r\n this.#wsPubSub.close(1000, msg.wsPubSubCloseByClient);\r\n this.#subscriptionsStack = new Map();\r\n this.#ws.close(1000, msg.wsCloseByClient);\r\n return (\r\n {\r\n status: \"success\",\r\n data: {\r\n message: msg.wsClosingWsPubSubClosingProcess\r\n }\r\n }\r\n )\r\n } else if (this.#wsPubSub == null && this.#ws !== null) {\r\n this.#ws.close(1000, msg.wsCloseByClient);\r\n return (\r\n {\r\n status: \"success\",\r\n data: {\r\n message: msg.wsClosingProcess\r\n }\r\n }\r\n )\r\n } else if (this.#wsPubSub != null && this.#ws == null) {\r\n this.#subscriptionsStack = new Map();\r\n this.#wsPubSub.close(1000, msg.wsCloseByClient);\r\n return (\r\n {\r\n status: \"success\",\r\n data: {\r\n message: msg.wsPubSubClosingProcess\r\n }\r\n }\r\n )\r\n } else {\r\n return (\r\n {\r\n status: \"error\",\r\n data: {\r\n message: msg.wsClosingError\r\n }\r\n }\r\n )\r\n }\r\n }\r\n if (!closePubSub) {\r\n if (this.#ws !== null) {\r\n this.#ws.close(1000, msg.wsCloseByClient);\r\n return (\r\n {\r\n status: \"success\",\r\n data: {\r\n message: msg.wsClosingProcess\r\n }\r\n }\r\n )\r\n } else {\r\n return (\r\n {\r\n status: \"error\",\r\n data: {\r\n message: msg.wsClosingError\r\n }\r\n }\r\n )\r\n }\r\n }\r\n }\r\n\r\n /*\r\n connectionState method returns the actual state of websocket connection\r\n */\r\n get connectionState() {\r\n let connectionStateString = msg.wsNotExist;\r\n if (this.#ws !== null) {\r\n let connectionState = this.#ws.readyState;\r\n switch (connectionState) {\r\n case 0:\r\n connectionStateString = msg.wsConnecting;\r\n break;\r\n case 1:\r\n connectionStateString = msg.wsOpen;\r\n break;\r\n case 2:\r\n connectionStateString = msg.wsClosing;\r\n break;\r\n case 3:\r\n connectionStateString = msg.wsClosed;\r\n break;\r\n default:\r\n connectionStateString = msg.wsNotExist;\r\n }\r\n }\r\n return connectionStateString;\r\n }\r\n\r\n /*\r\n pubSubState method returns the actual state of pubSubState websocket connection\r\n */\r\n get pubSubState() {\r\n let connectionStateString = msg.wsPubSubNotExist;\r\n if (this.#wsPubSub !== null) {\r\n let connectionState = this.#wsPubSub.readyState;\r\n switch (connectionState) {\r\n case 0:\r\n connectionStateString = msg.wsPubSubConnecting;\r\n break;\r\n case 1:\r\n connectionStateString = msg.wsPubSubOpen;\r\n break;\r\n case 2:\r\n connectionStateString = msg.wsPubSubClosing;\r\n break;\r\n case 3:\r\n connectionStateString = msg.wsPubSubClosed;\r\n break;\r\n default:\r\n connectionStateString = msg.wsPubSubNotExist;\r\n }\r\n }\r\n return connectionStateString;\r\n }\r\n\r\n /*\r\n requestsStackState method return the lits of pending requests\r\n */\r\n get requestsStackState() {\r\n return this.#requestsStack;\r\n }\r\n\r\n /*\r\n subscriptionsStackState method return the lits of active subscriptions\r\n */\r\n get subscriptionsStackState() {\r\n return this.#subscriptionsStack;\r\n }\r\n\r\n\r\n /*\r\n exec method calls the private method promiseSend building the object \r\n */\r\n async exec(command) {\r\n if (this.#ws !== null) {\r\n try {\r\n const response = await this.#promiseSend(\r\n {\r\n id: this.#makeid(5),\r\n type: \"exec\",\r\n command: command\r\n }\r\n );\r\n return (response);\r\n } catch (error) {\r\n return {\r\n status: \"error\",\r\n data: error\r\n };\r\n }\r\n } else {\r\n return {\r\n status: \"error\",\r\n data: {\r\n message: msg.wsExecErrorNoConnection\r\n }\r\n }\r\n }\r\n }\r\n\r\n\r\n /*\r\n notify method calls the private method promiseSend building the object \r\n */\r\n async notify(channel, payload) {\r\n if (this.#ws !== null) {\r\n try {\r\n const response = await this.#promiseSend(\r\n {\r\n id: this.#makeid(5),\r\n type: \"notify\",\r\n channel: channel,\r\n payload: JSON.stringify(payload)\r\n }\r\n );\r\n return (response);\r\n } catch (error) {\r\n return {\r\n status: \"error\",\r\n data: error\r\n };\r\n }\r\n } else {\r\n return (\r\n {\r\n status: \"error\",\r\n data: {\r\n message: msg.wsNotifyErrorNoConnection\r\n }\r\n }\r\n );\r\n }\r\n }\r\n\r\n\r\n /*\r\n listenChannel method calls the private method #pubsub to register to a new channel \r\n */\r\n async listenChannel(channel, callback) {\r\n if (this.#ws !== null) {\r\n try {\r\n const response = await this.#pubsub(\"listenChannel\", channel, callback);\r\n return response;\r\n } catch (error) {\r\n return ({\r\n status: \"error\",\r\n data: error\r\n });\r\n }\r\n } else {\r\n return ({\r\n status: \"error\",\r\n message: msg.wsListenError.errorNoConnection\r\n })\r\n }\r\n }\r\n\r\n\r\n /*\r\n listenTable method calls the private method #pubsub to register to a new table \r\n */\r\n async listenTable(table, callback) {\r\n if (this.#ws !== null) {\r\n try {\r\n const response = await this.#pubsub(\"listenTable\", table, callback);\r\n return response;\r\n } catch (error) {\r\n return ({\r\n status: \"error\",\r\n data: error\r\n });\r\n }\r\n } else {\r\n return ({\r\n status: \"error\",\r\n message: msg.wsListenError.errorNoConnection\r\n })\r\n }\r\n }\r\n\r\n\r\n\r\n /*\r\n unlistenChannel method calls the private method #pubsub to unregister to a channel \r\n */\r\n async unlistenChannel(channel) {\r\n if (!this.#subscriptionsStack.has(channel)) {\r\n return (\r\n {\r\n status: \"error\",\r\n data: {\r\n message: msg.wsUnlistenError.missingSubscritption + \" \" + channel\r\n }\r\n }\r\n )\r\n }\r\n\r\n if (this.#ws !== null) {\r\n try {\r\n const response = await this.#pubsub(\"unlistenChannel\", channel, null);\r\n return (response);\r\n } catch (error) {\r\n return {\r\n status: \"error\",\r\n data: error\r\n };\r\n }\r\n } else {\r\n return (\r\n {\r\n status: \"error\",\r\n data: {\r\n message: msg.wsUnlistenError.errorNoConnection\r\n }\r\n }\r\n )\r\n }\r\n }\r\n\r\n\r\n /*\r\n unlistenTable method calls the private method #pubsub to unregister to a table \r\n */\r\n async unlistenTable(table) {\r\n if (!this.#subscriptionsStack.has(table)) {\r\n return (\r\n {\r\n status: \"error\",\r\n data: {\r\n message: msg.wsUnlistenError.missingSubscritption + \" \" + table\r\n }\r\n }\r\n )\r\n }\r\n\r\n if (this.#ws !== null) {\r\n try {\r\n const response = await this.#pubsub(\"unlistenTable\", table, null);\r\n return (response);\r\n } catch (error) {\r\n return {\r\n status: \"error\",\r\n data: error\r\n };\r\n }\r\n } else {\r\n return (\r\n {\r\n status: \"error\",\r\n data: {\r\n message: msg.wsUnlistenError.errorNoConnection\r\n }\r\n }\r\n )\r\n }\r\n }\r\n\r\n /*\r\n listChannels method calls exec method to receive the list of all the existing channels \r\n */\r\n async listChannels() {\r\n try {\r\n const response = await this.exec(\"LIST CHANNELS\");\r\n return (response);\r\n } catch (error) {\r\n return {\r\n status: \"error\",\r\n data: error\r\n };\r\n }\r\n }\r\n\r\n /*\r\n createChannel method calls exec method to create a new channel\r\n channelName: mandatory, the name of the channel to be created\r\n ifNotExist: optional, if true set in the command the string [IF NOT EXISTS]\r\n */\r\n async createChannel(channelName, ifNotExist = true) {\r\n try {\r\n //params validation\r\n //check channelName has been provided\r\n if (!channelName) {\r\n return (\r\n {\r\n status: \"error\",\r\n data: {\r\n message: msg.createChannelErr.mandatory\r\n }\r\n }\r\n )\r\n }\r\n if (typeof channelName !== \"string\") {\r\n return (\r\n {\r\n status: \"error\",\r\n data: {\r\n message: msg.createChannelErr.string\r\n }\r\n }\r\n )\r\n }\r\n //params are ok\r\n let command = `CREATE CHANNEL '${channelName}'`;\r\n command = ifNotExist ? command + \" IF NOT EXISTS\" : command;\r\n const response = await this.exec(command);\r\n return (response);\r\n } catch (error) {\r\n return {\r\n status: \"error\",\r\n data: error\r\n };\r\n }\r\n }\r\n\r\n /*\r\n removeChannel method calls exec method to remove an existing channel \r\n */\r\n async removeChannel(channelName) {\r\n try {\r\n //params validation\r\n //check channelName has been provided\r\n if (!channelName) {\r\n return (\r\n {\r\n status: \"error\",\r\n data: {\r\n message: msg.removeChannelErr.mandatory\r\n }\r\n }\r\n )\r\n }\r\n if (typeof channelName !== \"string\") {\r\n return (\r\n {\r\n status: \"error\",\r\n data: {\r\n message: msg.removeChannelErr.string\r\n }\r\n }\r\n )\r\n }\r\n //params are ok\r\n let command = `REMOVE CHANNEL '${channelName}'`;\r\n const response = await this.exec(command);\r\n return (response);\r\n } catch (error) {\r\n return {\r\n status: \"error\",\r\n data: error\r\n };\r\n }\r\n }\r\n\r\n\r\n /* PRIVATE METHODS */\r\n\r\n /*\r\n makeid method generates unique IDs to use for requests\r\n */\r\n #makeid(length = 5) {\r\n var result = '';\r\n var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\r\n var charactersLength = characters.length;\r\n for (var i = 0; i < length; i++) {\r\n result += characters.charAt(Math.floor(Math.random() *\r\n charactersLength));\r\n }\r\n return result;\r\n }\r\n\r\n /*\r\n connectWs private method is called to create the websocket conenction used to send command request, receive command request response, pubSub subscription request, \r\n */\r\n #connectWs(url, errorMessage) {\r\n return new Promise((resolve, reject) => {\r\n var ws = new WebSocket(url);\r\n ws.onopen = function () {\r\n resolve(ws);\r\n };\r\n ws.onerror = function (err) {\r\n reject({\r\n err: err,\r\n message: errorMessage\r\n });\r\n };\r\n });\r\n }\r\n\r\n /*\r\n onCloseWs private method is called when close event is fired by websocket.\r\n if user provided a callback, this is invoked\r\n */\r\n #onCloseWs = (event) => {\r\n if (this.onClose !== null) {\r\n this.onClose(msg.wsCloseComplete);\r\n this.#ws.removeEventListener('close', this.#onCloseWs);\r\n this.#ws.removeEventListener('error', this.#onErrorWs);\r\n this.#ws = null;\r\n }\r\n }\r\n\r\n /*\r\n onErrorWs private method is called when error event is fired by websocket.\r\n if user provided a callback, this is invoked\r\n */\r\n #onErrorWs = (event) => {\r\n if (this.onError !== null) {\r\n this.onError(event, msg.wsOnError);\r\n }\r\n }\r\n\r\n /*\r\n onCloseWsPubSub private method is called when close event is fired by websocket.\r\n if user provided a callback, this is invoked\r\n */\r\n #onCloseWsPubSub = (event) => {\r\n if (this.onClose !== null) {\r\n this.onClose(msg.wsPubSubCloseComplete);\r\n this.#wsPubSub.removeEventListener('close', this.#onCloseWsPubSub);\r\n this.#wsPubSub.removeEventListener('error', this.#onErrorWsPubSub);\r\n this.#uuid = null;\r\n this.#wsPubSub = null;\r\n }\r\n }\r\n\r\n /*\r\n onError private method is called when error event is fired by websocket.\r\n if user provided a callback, this is invoked\r\n */\r\n #onErrorWsPubSub = (event) => {\r\n if (this.onError !== null) {\r\n this.onError(event, msg.wsPubSubOnError);\r\n }\r\n }\r\n\r\n /*\r\n pubsub method calls \r\n */\r\n async #pubsub(type, channel, callback) {\r\n //based on the value of of callback, create a new subscription or remove the subscription\r\n if (callback !== null) {\r\n //check if the channel subscription is already active\r\n if (!this.#subscriptionsStack.has(channel)) {\r\n //if the subscription does not exist \r\n try {\r\n let body;\r\n //based on type value build the right body\r\n //important in case of channel, the channel key is present in the body\r\n //important in case of table, the table key is present in the body\r\n if (type == \"listenChannel\") {\r\n body = {\r\n id: this.#makeid(5),\r\n type: \"listen\",\r\n channel: channel.toLowerCase(),\r\n }\r\n }\r\n if (type == \"unlistenChannel\") {\r\n body = {\r\n id: this.#makeid(5),\r\n type: \"unlisten\",\r\n channel: channel.toLowerCase(),\r\n }\r\n }\r\n if (type == \"listenTable\") {\r\n body = {\r\n id: this.#makeid(5),\r\n type: \"listen\",\r\n table: channel.toLowerCase(),\r\n }\r\n }\r\n if (type == \"unlistenTable\") {\r\n body = {\r\n id: this.#makeid(5),\r\n type: \"unlisten\",\r\n table: channel.toLowerCase(),\r\n }\r\n }\r\n const response = await this.#promiseSend(body);\r\n //if this is the first subscription, create the websocket connection dedicated to receive pubSub messages\r\n if (this.#subscriptionsStack.size == 0 && response.status == \"success\") {\r\n //response here we have authentication information\r\n const uuid = response.data.uuid;\r\n const secret = response.data.secret;\r\n try {\r\n this.#wsPubSubUrl = `wss://web1.sqlitecloud.io:8443/api/v1/wspsub?uuid=${uuid}&secret=${secret}`;\r\n this.#wsPubSub = await this.#connectWs(this.#wsPubSubUrl, \"PubSub connection not established\");\r\n //when the PubSub WebSocket is created the channel is added to the stack\r\n this.#subscriptionsStack.set(channel.toLowerCase(),\r\n {\r\n channel: channel.toLowerCase(),\r\n callback: callback\r\n }\r\n );\r\n this.#uuid = uuid;\r\n //register the onmessage event on pubSub websocket\r\n this.#wsPubSub.addEventListener('message', this.#wsPubSubonMessage);\r\n //register the close event on websocket\r\n this.#wsPubSub.addEventListener('error', this.#onErrorWsPubSub);\r\n //register the close event on websocket\r\n this.#wsPubSub.addEventListener('close', this.#onCloseWsPubSub);\r\n } catch (error) {\r\n return {\r\n status: \"error\",\r\n data: error\r\n };\r\n }\r\n }\r\n //if this isn't the first subscription, just add the supscription to the stack\r\n if (this.#subscriptionsStack.size > 0 && response.status == \"success\") {\r\n this.#subscriptionsStack.set(channel.toLowerCase(),\r\n {\r\n channel: channel.toLowerCase(),\r\n callback: callback\r\n }\r\n );\r\n }\r\n //build the object returned to client\r\n let userResponse = {};\r\n userResponse.status = response.status;\r\n if (response.status == \"success\") {\r\n userResponse.data = {};\r\n userResponse.data.channel = response.data.channel;\r\n }\r\n if (response.status == \"error\") {\r\n userResponse.data = response.data;\r\n }\r\n return userResponse;\r\n } catch (error) {\r\n return {\r\n status: \"error\",\r\n data: error\r\n };\r\n }\r\n } else {\r\n //if the subscription exists\r\n return (\r\n {\r\n status: \"warning\",\r\n data: {\r\n message: msg.wsListenError.alreadySubscribed + \" \" + channel\r\n }\r\n }\r\n )\r\n }\r\n } else {\r\n try {\r\n const response = await this.#promiseSend(\r\n {\r\n id: this.#makeid(5),\r\n type: type,\r\n channel: channel.toLowerCase(),\r\n }\r\n );\r\n this.#subscriptionsStack.delete(channel)\r\n //check the remaing active subscription. If zero the websocket connection used for pubSub can be closed\r\n if (this.#subscriptionsStack.size == 0) {\r\n this.#wsPubSub.removeEventListener('message', this.#wsPubSubonMessage);\r\n this.#wsPubSub.close(1000, msg.wsPubSubCloseByClient);\r\n this.#wsPubSub = null;\r\n }\r\n return (response);\r\n } catch (error) {\r\n return {\r\n status: \"error\",\r\n data: error\r\n };\r\n }\r\n }\r\n }\r\n\r\n /*\r\n wsPubSubonMessage private method is called when ad onmessage event is fired on pubSub websocket.\r\n based on the channel indicated in the message the right callback is called\r\n */\r\n #wsPubSubonMessage = (event) => {\r\n const pubSubMessage = JSON.parse(event.data);\r\n\r\n //since payload can be both a string or JSON, this function based on check of it is or not a valid JSON return the correct parsed paylod\r\n function payloadParser(str) {\r\n try {\r\n JSON.parse(str);\r\n } catch (e) {\r\n return str;\r\n }\r\n return JSON.parse(str);\r\n }\r\n\r\n //build the obj returned to the user removing fields not usefull\r\n const userPubSubMessage = {\r\n channel: pubSubMessage.channel,\r\n sender: pubSubMessage.sender,\r\n payload: payloadParser(pubSubMessage.payload),\r\n ownMessage: this.#uuid == pubSubMessage.sender\r\n }\r\n //this is the case in which the user decide to filter the message sent by himself\r\n if (this.filterSentMessages && this.#uuid == pubSubMessage.sender) {\r\n\r\n } else {\r\n this.#subscriptionsStack.get(pubSubMessage.channel).callback(userPubSubMessage);\r\n }\r\n }\r\n\r\n /*\r\n promiseSend private method send request to the server creating a Promise that resolve when a websocket onmessage event is fired.\r\n */\r\n #promiseSend(request) {\r\n //request is sent to the server\r\n this.#ws.send(JSON.stringify(request));\r\n //extract request id\r\n const requestId = request.id;\r\n //define the Promise that wait for the server response \r\n return new Promise((resolve, reject) => {\r\n //define what to do if an answer does not arrive within the set time\r\n const onRequestTimeout = setTimeout(() => { this.#handlePromiseRejectTimeout(requestId) }, this.requestTimeout);\r\n //add the new request to the request stack \r\n this.#requestsStack.set(\r\n requestId,\r\n {\r\n id: requestId,\r\n onRequestTimeout: onRequestTimeout,\r\n resolve: resolve,\r\n reject: reject\r\n }\r\n )\r\n //if this is the only one request in the stack, register the function to be executed at the onmessage event\r\n if (this.#requestsStack.size == 1) this.#ws.addEventListener('message', this.#handlePromiseResolve);\r\n })\r\n }\r\n\r\n /*\r\n private handlePromiseResolve method is called when onmessage event is fired.\r\n */\r\n #handlePromiseResolve = (event) => {\r\n //parse the response sent by the server\r\n const response = JSON.parse(event.data);\r\n //search in the requests stack the request with the same id of the response received by the server.\r\n //it is possible that the request no longer exists as it may have timed out and therefore deleted from the stack.\r\n //if the request was found:\r\n // - the Promise corresponding to the request is resolved returning the response received by the server\r\n // - the timeout related to the request is cleared\r\n // - the new requests stack is stored\r\n // - if there are no pending requests remove the websocket onmessage event\r\n if (this.#requestsStack.has(response.id)) {\r\n //build the obj returned to the user removing based on type\r\n let userResponse = {};\r\n switch (response.type) {\r\n case \"exec\":\r\n userResponse = {\r\n status: response.status,\r\n data: response.data\r\n };\r\n break;\r\n case \"notify\":\r\n userResponse = {\r\n status: response.status,\r\n data: response.data\r\n };\r\n break;\r\n case \"listen\":\r\n //in this case the message is passed as is because the uuid and secret field, if present, will be used to create wsPubSub connection\r\n //the messega will be cleaned from this fields in #pubsub method\r\n userResponse = response;\r\n break;\r\n case \"unlisten\":\r\n userResponse = {\r\n status: response.status,\r\n data: response.data\r\n };\r\n break;\r\n default:\r\n userResponse = response;\r\n }\r\n\r\n this.#requestsStack.get(response.id).resolve(userResponse);\r\n clearTimeout(this.#requestsStack.get(response.id).onRequestTimeout);\r\n this.#requestsStack.delete(response.id);\r\n if (this.#requestsStack.size == 0) this.#ws.removeEventListener('message', this.#handlePromiseResolve);\r\n }\r\n }\r\n\r\n /*\r\n private handlePromiseRejectTimeout method is called when a request times out.\r\n */\r\n #handlePromiseRejectTimeout = (requestID) => {\r\n //search in the requests stack the request with the same id of the request that times out.\r\n //a new requests stack is created by removing the request that times out.\r\n //once the request is found:\r\n // - the Promise corresponding to the reject returning an error message\r\n // - the timeout related to the request is cleared\r\n // - the new requests stack is stored\r\n // - if there are no pending requests remove the websocket onmessage event\r\n if (this.#requestsStack.has(requestID)) {\r\n clearTimeout(this.#requestsStack.get(requestID).onRequestTimeout);\r\n this.#requestsStack.get(requestID).reject({\r\n message: msg.wsTimeoutError + \" \" + requestID\r\n });\r\n this.#requestsStack.delete(requestID);\r\n if (this.#requestsStack.size == 0) this.#ws.removeEventListener('message', this.#handlePromiseResolve);\r\n }\r\n }\r\n}\r\n\r\n\r\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// startup\n// Load entry module and return exports\n// This entry module used 'module' so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(763);\n"],"names":["root","factory","exports","module","define","amd","this","msg","alreadySubscribed","errorNoConnection","missingSubscritption","mandatory","string","SQLiteCloud","Map","requestTimeout","filterSentMessages","constructor","projectID","apikey","onError","onClose","url","setRequestTimeout","value","setFilterSentMessages","async","status","data","message","addEventListener","error","close","closePubSub","connectionState","connectionStateString","readyState","pubSubState","requestsStackState","subscriptionsStackState","command","id","type","channel","payload","JSON","stringify","callback","table","has","exec","channelName","ifNotExist","length","result","characters","charactersLength","i","charAt","Math","floor","random","errorMessage","Promise","resolve","reject","ws","WebSocket","onopen","onerror","err","event","removeEventListener","body","toLowerCase","response","size","uuid","secret","set","userResponse","delete","pubSubMessage","parse","userPubSubMessage","sender","str","e","payloadParser","ownMessage","get","request","send","requestId","onRequestTimeout","setTimeout","clearTimeout","requestID","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","__webpack_modules__","d","definition","key","o","Object","defineProperty","enumerable","obj","prop","prototype","hasOwnProperty","call"],"sourceRoot":""} \ No newline at end of file diff --git a/JS/example/sqlite-cloud-chat/.babelrc b/JS/example/sqlite-cloud-chat/.babelrc deleted file mode 100644 index 661d78e4..00000000 --- a/JS/example/sqlite-cloud-chat/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["@babel/preset-env", "@babel/preset-react"] -} \ No newline at end of file diff --git a/JS/example/sqlite-cloud-chat/.env b/JS/example/sqlite-cloud-chat/.env deleted file mode 100644 index 35883ef5..00000000 --- a/JS/example/sqlite-cloud-chat/.env +++ /dev/null @@ -1,6 +0,0 @@ -// .env -DEBUG=false -PROJECT_ID=f9cdd1d5-7d16-454b-8cc0-548dc1712c26 -# API_KEY=Rfk00KgQkqIzfqVuTmO87xVLWUwBos3zPzwbXw5UDVY -API_KEY=B24tAXTnXFYatN7mSXTPTIRXEEJRiH1lawMEdxmRrps -ROUTES_PREFIX="" \ No newline at end of file diff --git a/JS/example/sqlite-cloud-chat/.gitignore b/JS/example/sqlite-cloud-chat/.gitignore deleted file mode 100644 index e985853e..00000000 --- a/JS/example/sqlite-cloud-chat/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.vercel diff --git a/JS/example/sqlite-cloud-chat/app/assets/favicon/favicon.png b/JS/example/sqlite-cloud-chat/app/assets/favicon/favicon.png deleted file mode 100644 index f3437d5c..00000000 Binary files a/JS/example/sqlite-cloud-chat/app/assets/favicon/favicon.png and /dev/null differ diff --git a/JS/example/sqlite-cloud-chat/app/assets/logo/logo-dark@4x.png b/JS/example/sqlite-cloud-chat/app/assets/logo/logo-dark@4x.png deleted file mode 100644 index 5ca4bac9..00000000 Binary files a/JS/example/sqlite-cloud-chat/app/assets/logo/logo-dark@4x.png and /dev/null differ diff --git a/JS/example/sqlite-cloud-chat/app/assets/logo/logo-light@4x.png b/JS/example/sqlite-cloud-chat/app/assets/logo/logo-light@4x.png deleted file mode 100644 index c57be373..00000000 Binary files a/JS/example/sqlite-cloud-chat/app/assets/logo/logo-light@4x.png and /dev/null differ diff --git a/JS/example/sqlite-cloud-chat/app/src/components/App.js b/JS/example/sqlite-cloud-chat/app/src/components/App.js deleted file mode 100644 index 3addbaa7..00000000 --- a/JS/example/sqlite-cloud-chat/app/src/components/App.js +++ /dev/null @@ -1,22 +0,0 @@ -//core -import React, { Fragment, useEffect, useState } from 'react'; -//utils -import { logThis } from '../js/utils'; -//context -import { StateProvider } from '../context/StateContext'; -//components -import Layout from './Layout'; - -const App = () => { - if (process.env.DEBUG == 'true') logThis('App: ON RENDER TEST'); - return ( - - - - - - ); -} - - -export default App; \ No newline at end of file diff --git a/JS/example/sqlite-cloud-chat/app/src/components/ChannelElement.js b/JS/example/sqlite-cloud-chat/app/src/components/ChannelElement.js deleted file mode 100644 index 7169795a..00000000 --- a/JS/example/sqlite-cloud-chat/app/src/components/ChannelElement.js +++ /dev/null @@ -1,239 +0,0 @@ -//core -import React, { useRef, useEffect, useState, useContext } from "react"; -//react-router -import { useSearchParams } from 'react-router-dom'; -//date-fns -import { format, getTime } from 'date-fns'; -//utils -import { - logThis -} from '../js/utils'; -//context -import { StateContext } from "../context/StateContext"; -//components -import ChannelElementDropdown from './ChannelElementDropdown'; -import CircularLoader from './loaders/CircularLoader'; - -const ChannelElement = (props) => { - if (process.env.DEBUG == "true") logThis("ChannelElement: ON RENDER"); - //extract props - const client = props.client; - const index = props.index; - const name = props.name; - const showEditor = props.showEditor; - const selectionState = props.selectionState; - const setSelectedChannel = props.setSelectedChannel; - const setSelectedChannelIndex = props.setSelectedChannelIndex; - const setOpenMobMsg = props.setOpenMobMsg; - const reloadChannelsList = props.reloadChannelsList; - const setReloadChannelsList = props.setReloadChannelsList; - //ref to the element - const channelButtonRef = useRef(null); - //react router hooks used to set query string - const [searchParams, setSearchParams] = useSearchParams(); - //if present, this is the database whose tables you want to register to - const queryDBName = searchParams.get("dbName"); - //this state store the listen command result - const [listenResponse, setListenResponse] = useState(null); - //timestamp last message - const [msgTimestamp, setMsgTimestamp] = useState(""); - //read from context state dedicated to save all received messages - const { chsMapRef, setChsMap } = useContext(StateContext); - const [prevMsgLenght, setPrevMsgLenght] = useState(0); - const [alertNewMsg, setAlertNewMsg] = useState(0); - //we need to create a reference to context state since the listen callback is called inside an event listener - //see here https://medium.com/geographit/accessing-react-state-in-event-listeners-with-usestate-and-useref-hooks-8cceee73c559 - //whene a new message arrives it is added in the chsMap in correspondence of the element which has the channel name as its key - const listen = (message) => { - if (message.channel == name) { - let newChsMap = new Map(JSON.parse(JSON.stringify(Array.from(chsMapRef.current)))); - let newMessages = JSON.parse(JSON.stringify(newChsMap.get(name))); - message.time = format(new Date(), "yyyy-MM-dd' | 'HH:mm:ss") - message.timeMs = getTime(new Date()) - setMsgTimestamp(message.time); - newMessages.push(message); - newChsMap.set(name, newMessages) - chsMapRef.current = newChsMap; - setChsMap(newChsMap); - } - } - //when loading for the first time start listen for incoming messages - //then check if the actual query params is equal to channel name - //in this case set the selected channel equal to the channel index - useEffect(() => { - const registerToCh = async () => { - let response; - if (showEditor) { - response = await client.listenChannel(name, listen); - } else { - response = await client.listenTable(name, listen); - } - setListenResponse(response); - const queryChannel = searchParams.get("channel"); - if (queryChannel == name) { - setSelectedChannelIndex(index) - }; - let newChsMap = new Map(JSON.parse(JSON.stringify(Array.from(chsMapRef.current)))); - newChsMap.set(name, []); - chsMapRef.current = newChsMap; - setChsMap(newChsMap); - } - registerToCh(); - }, []) - //update the counter indicating the unread messages everytime the chsMap changes - useEffect(() => { - if (chsMapRef.current.get(name)) { - if (!selectionState && chsMapRef.current.get(name).length !== prevMsgLenght) { - setAlertNewMsg(chsMapRef.current.get(name).length - prevMsgLenght); - } else { - setAlertNewMsg(0); - setPrevMsgLenght(chsMapRef.current.get(name).length); - } - } - }, [chsMapRef.current]) - - //method called every time a channel is selected - //whene a channel is selected the counter for unread messages is set to zero - //the current channel name and current channel index are setted to the current clicked channel - //query strings are updated based on the clicked channel - const updateSelectChannel = (event) => { - channelButtonRef.current.blur(); - setAlertNewMsg(0); - setSelectedChannelIndex(index); - setSelectedChannel(name); - if (queryDBName !== null) { - setSearchParams({ - dbName: queryDBName, - channel: name, - }); - } else { - setSearchParams({ - channel: name - }); - } - setOpenMobMsg(false); - } - //hadle click that drop a channel - const [isDroppingChannel, setIsDroppingChannel] = useState(false); - const [isErrorDropping, setIsErrorDropping] = useState(null); - const removeChannel = async (event) => { - event.stopPropagation(); - //verify if the dropped channel is the one selected - setIsDroppingChannel(true); - setIsErrorDropping(null); - if (selectionState) { - //if the channel is the one selected - //remove from the query string the channel name, but veryfing that the db name if present is not removed - if (queryDBName !== null) { - setSearchParams({ - dbName: queryDBName - }); - } else { - setSearchParams({}); - } - // setShowMessages(false); - setSelectedChannelIndex(-1); - setSelectedChannel(""); - } - const response = await client.removeChannel(name); - if (process.env.DEBUG == "true") console.log(response); - if (response.status == "success") { - //remove from the Maps the dropped channel - let newChsMap = new Map(JSON.parse(JSON.stringify(Array.from(chsMapRef.current)))); - newChsMap.delete(name); - chsMapRef.current = newChsMap; - setChsMap(newChsMap); - //if createChannel is succesful reload channelsList - setReloadChannelsList(!reloadChannelsList); - } else { - setIsDroppingChannel(false); - setIsErrorDropping(response.data.message) - } - } - //render UI - return ( -
- { - // badge for unread messages - (listenResponse && listenResponse.status == "success" && alertNewMsg !== 0) && -
- - {alertNewMsg} - -
- } - { - // loader during listen - !listenResponse && - - } - { - // loader during dropping - isDroppingChannel && - - } - { - listenResponse && !isDroppingChannel && -
- {name.slice(0, 2).toUpperCase()} - { - // error indicator on listen - listenResponse.status == "error" && - - - - - } - { - // success indicator on listen - listenResponse.status == "success" && - - - - - } -
- } - { - listenResponse && !isDroppingChannel && -
- -
- } - { - showEditor && -
- -
- } -
- ); -} - - -export default ChannelElement; \ No newline at end of file diff --git a/JS/example/sqlite-cloud-chat/app/src/components/ChannelElementDropdown.js b/JS/example/sqlite-cloud-chat/app/src/components/ChannelElementDropdown.js deleted file mode 100644 index 4e53873f..00000000 --- a/JS/example/sqlite-cloud-chat/app/src/components/ChannelElementDropdown.js +++ /dev/null @@ -1,63 +0,0 @@ -//core -import React, { Fragment, useState } from 'react'; -//@headlessui -import { Menu, Transition } from '@headlessui/react' -//@heroicons -import { EllipsisVerticalIcon } from '@heroicons/react/20/solid' -//utils -import { - logThis, - classNames, -} from '../js/utils'; -export default function ChannelElementDropdown(props) { - if (process.env.DEBUG == "true") logThis("ChannelElementDropdown: ON RENDER"); - //extract params from opt - const removeChannel = props.removeChannel; - //hadle click that opens dropdown menu - const handleOpenDropdown = (event) => { - event.stopPropagation(); - } - //render UI - return ( - -
- - Open options - -
- - - -
- - {({ active }) => ( - - )} - -
-
-
-
- ) -} diff --git a/JS/example/sqlite-cloud-chat/app/src/components/ChannelsList.js b/JS/example/sqlite-cloud-chat/app/src/components/ChannelsList.js deleted file mode 100644 index daa5a10e..00000000 --- a/JS/example/sqlite-cloud-chat/app/src/components/ChannelsList.js +++ /dev/null @@ -1,55 +0,0 @@ -//core -import React, { useContext } from "react"; -//utils -import { logThis } from '../js/utils'; -//components -import ChannelElement from './ChannelElement' -import CircularLoader from './loaders/CircularLoader' - -const ChannelsList = (props) => { - if (process.env.DEBUG == "true") logThis("ChannelsList: ON RENDER"); - //extract props - const client = props.client; - let channelsList = props.channelsList; - const showEditor = props.showEditor; - const isLoadingChannels = props.isLoadingChannels; - const setSelectedChannel = props.setSelectedChannel; - const setOpenMobMsg = props.setOpenMobMsg; - const selectedChannelIndex = props.selectedChannelIndex; - const setSelectedChannelIndex = props.setSelectedChannelIndex; - const reloadChannelsList = props.reloadChannelsList; - const setReloadChannelsList = props.setReloadChannelsList; - //render UI - return ( - - ); -} - - -export default ChannelsList; \ No newline at end of file diff --git a/JS/example/sqlite-cloud-chat/app/src/components/CreateChannel.js b/JS/example/sqlite-cloud-chat/app/src/components/CreateChannel.js deleted file mode 100644 index 3ac5739e..00000000 --- a/JS/example/sqlite-cloud-chat/app/src/components/CreateChannel.js +++ /dev/null @@ -1,111 +0,0 @@ -//core -import React, { useState } from 'react'; -//utils -import { - logThis, -} from '../js/utils'; -//components -import Alert from './alert/Alert'; -import CircularLoader from './loaders/CircularLoader'; -/* -opt = { -} -*/ -export default function CreateChannel(props) { - if (process.env.DEBUG == "true") logThis("CreateChannel: ON RENDER"); - //extract params from opt - const client = props.client; - const setReloadChannelsList = props.setReloadChannelsList; - const reloadChannelsList = props.reloadChannelsList; - //hadle input channel name - const [isError, setIsError] = useState(null); - const [channelName, setChannelName] = useState(""); - const handleChange = (event) => { - setChannelName(event.target.value); - } - //hadle click that submit channel creation - const [isCreatingChannel, setIsCreatingChannel] = useState(false); - const createChannel = async () => { - setIsCreatingChannel(true); - setIsError(null); - const response = await client.createChannel(channelName, true); - if (process.env.DEBUG == "true") console.log(response); - if (response.status == "success") { - //if createChannel is succesful reload channelsList - setReloadChannelsList(!reloadChannelsList); - } else { - setIsError(response.data.message); - } - setChannelName(""); - setIsCreatingChannel(false); - } - const handleOnClick = async () => { - await createChannel(); - } - //method to handle key down - //if pressed enter key the message is sent - //if pressed enter+shift keys, a new line is created - const handleKey = async (event) => { - if (event.keyCode == 13) { - await createChannel(); - } else { - return false; - } - }; - //render UI - return ( -
-
-

Create channel

-
- { - isCreatingChannel && - - } - { - !isCreatingChannel && - <> -
-
- - -
-
- { - channelName && - - } - - } -
- { - isError && -
- -
- } -
-
- ) -} diff --git a/JS/example/sqlite-cloud-chat/app/src/components/Layout.js b/JS/example/sqlite-cloud-chat/app/src/components/Layout.js deleted file mode 100644 index f2a387c5..00000000 --- a/JS/example/sqlite-cloud-chat/app/src/components/Layout.js +++ /dev/null @@ -1,378 +0,0 @@ -//core -import React, { Fragment, useContext, useState, useEffect } from 'react'; -//react-router -import { useSearchParams } from 'react-router-dom'; -//SQLiteCloud -import SQLiteCloud from 'sqlitecloud-sdk' -/* -usare client come nome dell'istanza locale -*/ -//@heroicons/react -import { - Bars3Icon, - XMarkIcon -} from '@heroicons/react/24/outline' -//utils -import { - logThis, - checkChannelExist -} from '../js/utils'; -//image -import lightLogo from '../../assets/logo/logo-dark@4x.png'; -//context -import { StateContext } from "../context/StateContext" -//components -import Alert from './alert/Alert' -import CircularLoader from './loaders/CircularLoader' -import CreateChannel from './CreateChannel' -import ChannelsList from './ChannelsList' -import MessageEditor from './MessageEditor' -import MessagesList from './MessagesList' - -export default function Layout(props) { - if (process.env.DEBUG == "true") logThis("Layout: ON RENDER"); - //read from context all channels registered - const { chsMap } = useContext(StateContext); - //credentials to establish the websocket connection - var projectId = process.env.PROJECT_ID; - var apikey = process.env.API_KEY; - //state dedicated to SQLiteCloud instance used to handle websocket connection - const [client, setClient] = useState(null); - const [isConnecting, setIsConnecting] = useState(true); - const [connectionResponse, setConnectionResponse] = useState(null); - const [isLoadingChannels, setIsLoadingChannels] = useState(false); - const [channelsListResponse, setChannelsListResponse] = useState(null); - //callback function passed to websocket and register on error and close events - let onErrorCallback = function (event, msg) { - console.log(msg); - } - let onCloseCallback = function (msg) { - console.log(msg); - } - //react router hooks used to set & get query string - const [searchParams, setSearchParams] = useSearchParams(); - //if present, this is the database whose tables you want to register to - const queryDBName = searchParams.get('dbName'); - //if present, this is the channel you want to listen to - const queryChannel = searchParams.get('channel'); - //check if listening to the actual selected channel is completed - const isListeningActualChannel = chsMap ? chsMap.get(queryChannel) : null; - //state to handle actual selected channel - const [selectedChannel, setSelectedChannel] = useState(queryChannel); - const [selectedChannelIndex, setSelectedChannelIndex] = useState(-1); - //state used to store the available available channels. In case queryDBName !== null available channels are db tables - const [channelsList, setChannelsList] = useState(undefined); - //based on value of query channel show or not Messages component - const [showMessages, setShowMessages] = useState(false); - //show message editor and other ui element based on query parameters editor is shown when queryDBName is null - const [showEditor, setShowEditor] = useState(false); - //state used to open or close mobile sidebar holding messages - const [openMobMsg, setOpenMobMsg] = useState(false); - //method that handle channelsListResponse - const handlingChannelsListResponse = (channelsListResponse) => { - setChannelsListResponse(channelsListResponse); - //check how listChannels completed - if (channelsListResponse.status == 'success') { - //successful listChannels - if (process.env.DEBUG == 'true') logThis('Received channels list'); - var channels = []; - if (queryDBName !== null) { - channelsListResponse.data.rows.forEach(c => { - channels.push(c.chname); - }) - setChannelsList(channels); - setShowEditor(false); - } else { - if (channelsListResponse.data.rows == undefined) { - setChannelsList([]); - } else { - channelsListResponse.data.rows.forEach(c => { - channels.push(c.chname); - }) - setChannelsList(channels); - } - setShowEditor(true); - } - //check if the channel in query string exist - const testChannelExist = checkChannelExist(channels, queryChannel); - if (testChannelExist !== -1) { - //if true show message components - setShowMessages(true); - setSelectedChannelIndex(testChannelExist); - setOpenMobMsg(true); - } else { - //if false not show message components and remove query string from url - if (queryDBName !== null) { - setSearchParams({ - dbName: queryDBName - }); - } else { - setSearchParams({}); - } - setShowMessages(false); - setSelectedChannelIndex(-1); - setOpenMobMsg(false); - } - } else { - //error on listChannels - if (process.env.DEBUG == 'true') logThis(channelsListResponse.data.message); - } - setIsLoadingChannels(false); - } - //useEffect triggered only onMount - //create websocket connection - //retrieve channels list, or tables list if queryDBName is present as query string - useEffect(() => { - const onMountWrapper = async () => { - if (process.env.DEBUG == 'true') logThis('App: ON useEffect'); - //init SQLiteCloud instance using provided credentials - let localClient = new SQLiteCloud(projectId, apikey, onErrorCallback, onCloseCallback); - //set websocket request timeout - localClient.setRequestTimeout(5000); - //try to enstablish websocket connection - const connectionResponse = await localClient.connect(); - setConnectionResponse(connectionResponse); - setIsConnecting(false); - if (process.env.DEBUG == 'true') console.log(connectionResponse); - //check how websocket connection completed - if (connectionResponse.status == 'success') { - //successful websocket connection - setClient(localClient) - //based on query parameters select if retrieve tables db or channels - //in case of db, tables will be saved as channels - let channelsListResponse = null; - setIsLoadingChannels(true); - if (queryDBName !== null) { - const execMessage = `USE DATABASE ${queryDBName}; LIST TABLES PUBSUB` - channelsListResponse = await localClient.exec(execMessage); - } else { - channelsListResponse = await localClient.listChannels(); - } - handlingChannelsListResponse(channelsListResponse); - } else { - //error on websocket connection - if (process.env.DEBUG == 'true') logThis(connectionResponse.data.message); - } - } - onMountWrapper(); - }, []); - //useEffect triggered every time selectedChannel changes - useEffect(() => { - var testChannelExist = checkChannelExist(channelsList, selectedChannel); - if (testChannelExist == -1) { - setShowMessages(false); - setSelectedChannelIndex(testChannelExist); - setOpenMobMsg(false); - } - if (testChannelExist != -1) { - setShowMessages(true); - setSelectedChannelIndex(testChannelExist); - } - }, [selectedChannel]); - //section dedicated to handling channels list after a channel is created o dropped - const [reloadChannelsList, setReloadChannelsList] = useState(false); - useEffect(() => { - const onMountWrapper = async () => { - if (client) { - setIsLoadingChannels(true); - let channelsListResponse = await client.listChannels(); - handlingChannelsListResponse(channelsListResponse); - } - } - onMountWrapper(); - }, [reloadChannelsList]); - //method called to close channel - const closeChannel = () => { - //if false not show message components and remove query string from url - if (queryDBName !== null) { - setSearchParams({ - dbName: queryDBName - }); - } else { - setSearchParams({}); - } - setShowMessages(false); - setSelectedChannelIndex(-1); - setSelectedChannel(null); - setOpenMobMsg(false); - } - //useEffect triggered when chsMap change - useEffect(() => { - let lastMessagesTimestamp = []; - //build an array with the last received message for each channel - chsMap.forEach((ch, key) => { - const lastChEl = ch[ch.length - 1]; - if (lastChEl) { - lastMessagesTimestamp.push({ - name: key, - time: lastChEl.timeMs - }); - } else { - lastMessagesTimestamp.push({ - name: key, - time: 0 - }); - } - }); - //reordered the array from the old messages to the newest - lastMessagesTimestamp.sort(function (a, b) { - return ((a.time < b.time) ? -1 : ((a.time == b.time) ? 0 : 1)); - }); - //reverse the array to have as first the newest channel - lastMessagesTimestamp = lastMessagesTimestamp.reverse(); - //reorder channelsList following the new order based on last messages - if (channelsList) { - let newChannelsList = []; - let newSelectedIndex = -1; - lastMessagesTimestamp.forEach((lastMsg, i) => { - if (lastMsg.name === selectedChannel) newSelectedIndex = i; - newChannelsList.push(lastMsg.name); - }) - //check if all the original channels are present in the new array - //if a channel is not present is added to the array - //this can happen when there is a problem in listening a specific channel - channelsList.forEach((channel) => { - if (newChannelsList.indexOf(channel) === -1) { - newChannelsList.push(channel); - } - }) - setChannelsList(newChannelsList); - setSelectedChannelIndex(newSelectedIndex); - } - }, [chsMap]) - //show or not sidebar on mobile - const showSideBarOnMob = openMobMsg || selectedChannelIndex == -1; - //render UI - return ( -
- {/* Static sidebar for desktop */} -
-
- {/* Sidebar component, swap this element with another sidebar if you like */} -
- -
- { - showEditor && -
- -
- } - { - isConnecting && -
- -
- } -
-
- -
- {/* Sidebar footer for desktop */} -
-
-
-
-

project ID:

-

{projectId}

-
-
- { - queryDBName && -
-

database name:

-

{queryDBName}

-
- } -
-
-
-
-
- - {/* Layout right section */} -
- { - connectionResponse && connectionResponse.status == "error" && -
- -
- } - { - connectionResponse && connectionResponse.status == "success" && !showMessages && -
-
-
- Welcome to SQLite Cloud Chat! -
-
-
- This sample Web App demonstrates the power of the Pub/Sub capabilities built into SQLite Cloud. -
-
- Open this page on two or more separate devices and try to send messages to different channels. -
-
- The underline Javascript SDK communicates with an SQLite Cloud cluster, and it uses the standard PUB/SUB commands to efficiently broadcast messages between the channels. -
-
- Pub/Sub is also implemented on the database level so you can LISTEN to a database table and start receiving JSON payloads each time that table changes. -
-
-
-
- } - { - channelsListResponse && channelsListResponse.status == "error" && -
- -
- } - { - showMessages && isListeningActualChannel && -
-
-

{selectedChannel}

- -
-
- } -
- { - showMessages && isListeningActualChannel && - <> - { - chsMap && chsMap.size > 0 && - } - - } -
- { - showEditor && showMessages && isListeningActualChannel && -
- -
- } -
-
- ) -} diff --git a/JS/example/sqlite-cloud-chat/app/src/components/MessageEditor.js b/JS/example/sqlite-cloud-chat/app/src/components/MessageEditor.js deleted file mode 100644 index baabd4a7..00000000 --- a/JS/example/sqlite-cloud-chat/app/src/components/MessageEditor.js +++ /dev/null @@ -1,147 +0,0 @@ -//core -import React, { Fragment, useRef, useState, useEffect } from "react"; -//react-router -import { useSearchParams } from 'react-router-dom'; -//headlessui/react -// import { Listbox, Transition } from '@headlessui/react' -//heroicons/ -// import { CalendarIcon, PaperClipIcon, TagIcon, UserCircleIcon } from '@heroicons/react/20/solid' -//utils -import { - logThis -} from '../js/utils'; -//components -import Alert from './alert/Alert' -import CircularLoader from './loaders/CircularLoader'; -/* dummy data from demo component. can be usefull in future -const assignees = [ - { name: 'Unassigned', value: null }, - { - name: 'Wade Cooper', - value: 'wade-cooper', - avatar: - 'https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80', - }, - // More items... -] -const labels = [ - { name: 'Unlabelled', value: null }, - { name: 'Engineering', value: 'engineering' }, - // More items... -] -const dueDates = [ - { name: 'No due date', value: null }, - { name: 'Today', value: 'today' }, - // More items... -] -*/ -const MessageEditor = ({ client }) => { - if (process.env.DEBUG == "true") logThis("MessageEditor: ON RENDER"); - //react router hooks used to set query string - const [searchParams, setSearchParams] = useSearchParams(); - //store and set actual message written by the user - const [value, setValue] = useState(""); - //handling change in textarea - const handleChange = (event) => { - if (!sendingMessage) { - setValue(event.target.value); - } - }; - //init reference to textarea - const editorRef = useRef(null); - //when query parameters change, set automaticcally focus on textarea - useEffect(() => { - setValue(""); - setErrorSending(null); - editorRef.current.focus(); - }, [searchParams]) - /* state not used at the moment. taken from demo component - const [assigned, setAssigned] = useState(assignees[0]) - const [labelled, setLabelled] = useState(labels[0]) - const [dated, setDated] = useState(dueDates[0]) - */ - //method to handle key down - //if pressed enter key the message is sent - //if pressed enter+shift keys, a new line is created - const handleKey = (event) => { - if (event.keyCode == 13 && event.shiftKey) { - } else if (event.keyCode == 13) { - event.preventDefault(); - if (value !== "") sendMsg(); - return false; - } - }; - //state used to know if we are sending a message - const [sendingMessage, setSendingMessage] = useState(false); - //method used to send message - const [errorSending, setErrorSending] = useState(null); - const sendMsg = async (event) => { - if (event) event.preventDefault(); - const queryChannel = searchParams.get("channel"); - if (queryChannel && !sendingMessage && value) { - setSendingMessage(true); - setErrorSending(null); - const response = await client.notify(queryChannel, { message: value }); - console.log(response) //TOGLI - if (response.status == "success") { - setValue(""); - } - if (response.status == "error") { - setErrorSending(response.data.message); - } - setSendingMessage(false); - } - editorRef.current.focus(); - } - //render ui - return ( -
- { - errorSending && -
- -
- } -
-
- -
- -
- - -
- - - -
- - - - - - - diff --git a/PHP/admin/console_action.php b/PHP/admin/console_action.php deleted file mode 100644 index 02494db5..00000000 --- a/PHP/admin/console_action.php +++ /dev/null @@ -1,25 +0,0 @@ - 0, 'msg' => exec_lasterror(true)); - } else if ($rc instanceof SQLiteCloudRowset) { - $r = array('result' => 2, 'msg' => render_console_table($rc)); - } else if ($rc === true) { - $r = array('result' => 1, 'msg' => 'Query succesfully executed.'); - } else if ($rc === null) { - $r = array('result' => 1, 'msg' => 'NULL'); - } else { - $r = array('result' => 1, 'msg' => $rc); - } - - echo json_encode($r); -?> \ No newline at end of file diff --git a/PHP/admin/dashboard.css b/PHP/admin/dashboard.css deleted file mode 100644 index b71942ad..00000000 --- a/PHP/admin/dashboard.css +++ /dev/null @@ -1,103 +0,0 @@ -body { - font-size: .875rem; -} - -.feather { - width: 16px; - height: 16px; - vertical-align: text-bottom; -} - -/* - * Sidebar - */ - -.sidebar { - position: fixed; - top: 0; - bottom: 0; - left: 0; - z-index: 100; /* Behind the navbar */ - padding: 48px 0 0; /* Height of navbar */ - box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1); -} - -@media (max-width: 767.98px) { - .sidebar { - top: 5rem; - } -} - -.sidebar-sticky { - position: relative; - top: 0; - height: calc(100vh - 48px); - padding-top: .5rem; - overflow-x: hidden; - overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ -} - -@supports ((position: -webkit-sticky) or (position: sticky)) { - .sidebar-sticky { - position: -webkit-sticky; - position: sticky; - } -} - -.sidebar .nav-link { - font-weight: 500; - color: #333; -} - -.sidebar .nav-link .feather { - margin-right: 4px; - color: #999; -} - -.sidebar .nav-link.active { - color: #007bff; -} - -.sidebar .nav-link:hover .feather, -.sidebar .nav-link.active .feather { - color: inherit; -} - -.sidebar-heading { - font-size: .75rem; - text-transform: uppercase; -} - -/* - * Navbar - */ - -.navbar-brand { - padding-top: .75rem; - padding-bottom: .75rem; - font-size: 1rem; - background-color: rgba(0, 0, 0, .25); - box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25); -} - -.navbar .navbar-toggler { - top: .25rem; - right: 1rem; -} - -.navbar .form-control { - padding: .75rem 1rem; - border-width: 0; - border-radius: 0; -} - -.form-control-dark { - color: #fff; - background-color: rgba(255, 255, 255, .1); - border-color: rgba(255, 255, 255, .1); -} - -.form-control-dark:focus { - border-color: transparent; - box-shadow: 0 0 0 3px rgba(255, 255, 255, .25); -} diff --git a/PHP/admin/dashboard.js b/PHP/admin/dashboard.js deleted file mode 100644 index c0ce2093..00000000 --- a/PHP/admin/dashboard.js +++ /dev/null @@ -1,55 +0,0 @@ -/* globals Chart:false, feather:false */ - -(function () { - 'use strict' - - feather.replace() - - /* - // Graphs - var ctx = document.getElementById('myChart') - // eslint-disable-next-line no-unused-vars - var myChart = new Chart(ctx, { - type: 'line', - data: { - labels: [ - 'Sunday', - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday' - ], - datasets: [{ - data: [ - 15339, - 21345, - 18483, - 24003, - 23489, - 24092, - 12034 - ], - lineTension: 0, - backgroundColor: 'transparent', - borderColor: '#007bff', - borderWidth: 4, - pointBackgroundColor: '#007bff' - }] - }, - options: { - scales: { - yAxes: [{ - ticks: { - beginAtZero: false - } - }] - }, - legend: { - display: false - } - } - }) - */ -})() diff --git a/PHP/admin/databases.php b/PHP/admin/databases.php deleted file mode 100644 index eefa86f6..00000000 --- a/PHP/admin/databases.php +++ /dev/null @@ -1,46 +0,0 @@ - - -
- -

Databases

-
- ' . $value . ''; - } - $rs = query_listdatabases(true); - render_table($rs, "on_column"); - ?> -
- - - -
- - - diff --git a/PHP/admin/download.php b/PHP/admin/download.php deleted file mode 100644 index ce6c8118..00000000 --- a/PHP/admin/download.php +++ /dev/null @@ -1,25 +0,0 @@ - \ No newline at end of file diff --git a/PHP/admin/include/footer.php b/PHP/admin/include/footer.php deleted file mode 100644 index 6677aecb..00000000 --- a/PHP/admin/include/footer.php +++ /dev/null @@ -1,22 +0,0 @@ -
- - - - - - - - - - ' . $jscript . ''; - if (isset($jsinclude)) echo ''; - echo "\n"; - ?> - - - - \ No newline at end of file diff --git a/PHP/admin/include/header.php b/PHP/admin/include/header.php deleted file mode 100644 index 2cf04b64..00000000 --- a/PHP/admin/include/header.php +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - - - SQLite Cloud Dashboard - - - - - - - - - - - - - - -
-
- - \ No newline at end of file diff --git a/PHP/admin/index.php b/PHP/admin/index.php deleted file mode 100644 index fa857328..00000000 --- a/PHP/admin/index.php +++ /dev/null @@ -1,24 +0,0 @@ - - -
- -

Nodes

-
- -
- - -

-
- -
- -
- - diff --git a/PHP/admin/info.php b/PHP/admin/info.php deleted file mode 100644 index f801e56b..00000000 --- a/PHP/admin/info.php +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/PHP/admin/latency.php b/PHP/admin/latency.php deleted file mode 100644 index ebc6a4a9..00000000 --- a/PHP/admin/latency.php +++ /dev/null @@ -1,15 +0,0 @@ - - -
- -

Latency

-
- -
- -
- - diff --git a/PHP/admin/login.js b/PHP/admin/login.js deleted file mode 100644 index 1346b522..00000000 --- a/PHP/admin/login.js +++ /dev/null @@ -1,48 +0,0 @@ -const form = document.getElementById('login-form'); - -form.addEventListener('submit', function(event) { - event.preventDefault(); - - // sanity check - if (!this.hostname.value) return false; - if (!this.port.value) return false; - if (!this.username.value) return false; - if (!this.password.value) return false; - - const data = { - hostname: this.hostname.value, - port: this.port.value, - username: this.username.value, - password: this.password.value - }; - - postData(data).then (reply => { - if (reply['result'] == 0) { - var div = document.getElementById('message'); - div.innerHTML = reply['msg']; - div.style.display = "block"; - return; - } - location.href = '/index.php'; - }); -}); - -async function postData(data) { - try { - const response = await fetch ('/login_action.php', { - method: 'POST', - body: JSON.stringify(data) - } - ); - - const reply = await response.json(); - return reply; - - } catch (error) { - const reply = { - result: 0, - msg: error - }; - return JSON.stringify(reply); - } -} \ No newline at end of file diff --git a/PHP/admin/login.php b/PHP/admin/login.php deleted file mode 100644 index be3f593a..00000000 --- a/PHP/admin/login.php +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - SQLite Cloud Admin - - - -
- -
- -

Dashboard Admin

-
- -
-
- -
- - - -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- -
- -
- -
-
-
- - - - - - - - diff --git a/PHP/admin/login_action.php b/PHP/admin/login_action.php deleted file mode 100644 index 428d617b..00000000 --- a/PHP/admin/login_action.php +++ /dev/null @@ -1,26 +0,0 @@ - 1); - $_SESSION['start'] = time(); - $_SESSION['port'] = $port; - $_SESSION['hostname'] = $hostname; - $_SESSION['username'] = $username; - $_SESSION['password'] = $password; - } else { - // error - $r = array('result' => 0, 'msg' => $rc); - } - - echo json_encode($r); -?> \ No newline at end of file diff --git a/PHP/admin/logout.php b/PHP/admin/logout.php deleted file mode 100644 index 908b2120..00000000 --- a/PHP/admin/logout.php +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - SQLite Cloud Admin - - - -
- -
- -

Goodbye!

-
- - - - - - - diff --git a/PHP/admin/logs.php b/PHP/admin/logs.php deleted file mode 100644 index dfcb6268..00000000 --- a/PHP/admin/logs.php +++ /dev/null @@ -1,45 +0,0 @@ - - -
- -

Logs

-
- PANIC'; - case 1: return 'FATAL'; - case 2: return 'ERROR'; - case 3: return 'WARNING'; - case 4: return 'INFO'; - case 5: return 'DEBUG'; - default: return $value; - } - } - - return false; - } - $rs = query_listlogs(50); - render_table($rs, 'on_column'); - ?> -
- -
- - diff --git a/PHP/admin/plugins.php b/PHP/admin/plugins.php deleted file mode 100644 index 56c89521..00000000 --- a/PHP/admin/plugins.php +++ /dev/null @@ -1,15 +0,0 @@ - - -
- -

Plugins

-
- -
- -
- - diff --git a/PHP/admin/settings.php b/PHP/admin/settings.php deleted file mode 100644 index b3d7f368..00000000 --- a/PHP/admin/settings.php +++ /dev/null @@ -1,15 +0,0 @@ - - -
- -

Settings

-
- -
- -
- - diff --git a/PHP/admin/stats.php b/PHP/admin/stats.php deleted file mode 100644 index 44daf5af..00000000 --- a/PHP/admin/stats.php +++ /dev/null @@ -1,37 +0,0 @@ - - -
- - -

Memory Usage

- - -

Bytes In

- - -

Bytes Out

- - -

CPU Usage

- - - - -
- - - - diff --git a/PHP/admin/upload.js b/PHP/admin/upload.js deleted file mode 100644 index 77a96340..00000000 --- a/PHP/admin/upload.js +++ /dev/null @@ -1,186 +0,0 @@ -const form = document.getElementById('upload-form'); -const enableLogging = true; - -const actionTypeUploadStart = 1 -const actionTypeUploadEnd = 2 -const actionTypeUploadError = 3 -const actionTypeUploadLoop = 4 - -form.addEventListener('submit', function(event) { - event.preventDefault(); - - if (!this.datafile.value) return false; - - var f = document.getElementById('datafile'); - var k = document.getElementById('enckey'); - - var file = f.files[0]; - var size = file.size; - var key = (k.value && k.value.length) ? k.value : null; - var sliceSize = 1024*1024; - var start = 0; - - var result = uploadDatabase(f.value, key, file, size, sliceSize); -}); - -function uploadDatabase(path, key, file, size, chunkSize) { - if (enableLogging) console.log("uploadDatabase: " + path + " key: " + key + " size: " + size + " chunk: " + chunkSize); - - if (uploadStart(path, key, size, chunkSize) == false) return false; - if (uploadLoop(file, 0, size, chunkSize) == false) return false; - return true; -} - -function uploadStart(file, key, size, chunkSize) { - resetUI(actionTypeUploadStart); - - var name = file.split(/(\\|\/)/g).pop(); - if (enableLogging) console.log('uploadStart ' + name); - - var formdata = new FormData(); - formdata.append('action', 0); // START - formdata.append('name', name); - if (key) formdata.append('key', key); - - var reqcount = Math.round((size / chunkSize) + 50); - if (enableLogging) console.log("reqcount: " + reqcount); - - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/upload_action.php', false); - xhr.setRequestHeader("Connection", "keep-alive"); - xhr.setRequestHeader("Keep-Alive", "timeout=15, max=" + reqcount + "\""); - xhr.send(formdata); - - return true; -} - -function uploadEnd() { - resetUI(actionTypeUploadEnd); - - if (enableLogging) console.log('uploadEnd'); - - var formdata = new FormData(); - formdata.append('action', 2); // END - - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/upload_action.php', false); - xhr.send(formdata); - - progressSet(100); - displayMessage("Database succesfully uploaded."); - - setTimeout(() => {location.href = '/databases.php';}, 500); - return true; -} - -function uploadAbort() { - resetUI(actionTypeUploadError); - - if (enableLogging) console.log('uploadAbort'); - - var formdata = new FormData(); - formdata.append('action', 666); // ABORT - - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/upload_action.php', false); - xhr.send(formdata); - - return true; -} - -function uploadLoop (file, start, end, size) { - // compute local values - var islast = false; - var len = size; - if (start + len > end) {len = end - size; islast = true;} - if (len < 0) len = end; - - // send next/final chunk ONLY after the previous one has been sent - var xhr = new XMLHttpRequest(); - xhr.onreadystatechange = function () { - if (xhr.readyState == XMLHttpRequest.DONE) { - if (this.responseText != 0) { - uploadAbort(); - displayError(this.responseText); - return; - } - - var value = Math.floor(( start / end) * 100); - progressSet(value); - (islast) ? uploadEnd() : uploadLoop(file, start + size, end, size); - } - } - - // compute chunk to send - var chunk = slice(file, start, start + len); - - // chunk is now a Blob that can only be read async - const reader = new FileReader(); - reader.onloadend = function () { - // prepare parameters - if (enableLogging) console.log('uploadChunk: ' + start + ' ' + len); - - var formdata = new FormData(); - formdata.append('action', 1); // UPLOAD - formdata.append('start', start); - formdata.append('len', len); - formdata.append('end', end); - formdata.append('chunk', reader.result); // reader.result contains the contents of blob - formdata.append('encoding', 1); // 1 -> base64, 2 -> binary - - // post data - xhr.open('POST', '/upload_action.php', true); - xhr.send(formdata); - } - reader.readAsDataURL(chunk); - - return true; -} - -function slice(file, start, end) { - var slice = file.mozSlice ? file.mozSlice : - file.webkitSlice ? file.webkitSlice : - file.slice ? file.slice : noop; - return slice.bind(file)(start, end); -} - -function noop() { -} - -function displayError(msg) { - var errorID = document.getElementById('upload_error'); - errorID.innerHTML = msg; - errorID.style.display = "block"; -} - -function displayMessage(msg) { - var messageID = document.getElementById('upload_message'); - messageID.innerHTML = '' + msg + ''; - messageID.style.display = "block"; -} - -function resetUI(actionType) { - var errorID = document.getElementById('upload_error'); - var messageID = document.getElementById('upload_message'); - var progressID = document.getElementById('upload_progress'); - var buttonID = document.getElementById('upload_button'); - - var showProgress = (actionType == actionTypeUploadStart); - var hideProgrss = (actionType == actionTypeUploadEnd); - var hideError = (actionType == actionTypeUploadStart); - var enableButton = (actionType == actionTypeUploadEnd) || (actionType == actionTypeUploadError); - var disableButton = (actionType == actionTypeUploadStart); - var hideMessage = (actionType == actionTypeUploadStart); - - if (showProgress) progressID.style.display = "block"; - if (hideProgrss) progressID.style.display = "none"; - if (hideError) errorID.style.display = "none"; - if (hideMessage) messageID.style.display = "none"; - if (enableButton) buttonID.disabled = false; - if (disableButton) buttonID.disabled = true; -} - -function progressSet(value) { - var progressID = document.getElementById('upload_progress'); - progressID.value = value; -} diff --git a/PHP/admin/upload_action.php b/PHP/admin/upload_action.php deleted file mode 100644 index 069c90dd..00000000 --- a/PHP/admin/upload_action.php +++ /dev/null @@ -1,58 +0,0 @@ - \ No newline at end of file diff --git a/PHP/admin/users.php b/PHP/admin/users.php deleted file mode 100644 index e01f249f..00000000 --- a/PHP/admin/users.php +++ /dev/null @@ -1,15 +0,0 @@ - - -
- -

Users

-
- -
- -
- - diff --git a/PHP/sqcloud.php b/PHP/sqcloud.php deleted file mode 100644 index f97ff924..00000000 --- a/PHP/sqcloud.php +++ /dev/null @@ -1,723 +0,0 @@ -= $this->nrows) return -1; - if ($col < 0 || $col >= $this->ncols) return -1; - return $row*$this->ncols+$col; - } - - public function value ($row, $col) { - $index = $this->compute_index($row, $col); - if ($index < 0) return NULL; - return $this->data[$index]; - } - - public function name ($col) { - if ($col < 0 || $col >= $this->ncols) return NULL; - return $this->colname[$col]; - } - - public function dump () { - print("version: {$this->version}\n"); - print("nrows: {$this->nrows}\n"); - print("ncols: {$this->ncols}\n"); - - print("colname: "); - print_r($this->colname); - print("\n"); - - if ($this->version == 2) { - print("dbname: "); - print_r($this->dbname); - print("\n"); - - print("tblname: "); - print_r($this->tblname); - print("\n"); - - print("origname: "); - print_r($this->origname); - print("\n"); - } - - if ($this->data && count($this->data) > 0) { - print("data: "); - print_r($this->data); - print("\n"); - } - } - } - - class SQLiteCloud { - public const SDKVersion = '1.0.0'; - - public $username = ''; - public $password = ''; - public $database = ''; - public $timeout = NULL; - public $connect_timeout = 20; - public $compression = false; - public $sqlitemode = false; - public $zerotext = false; - public $insecure = false; - public $tls_root_certificate = NULL; - public $tls_certificate = NULL; - public $tls_certificate_key = NULL; - - public $errmsg = NULL; - public $errcode = 0; - public $xerrcode = 0; - - private $socket = NULL; - private $isblob = false; - private $rowset = NULL; - - // PUBLIC - public function connect ($hostname = "localhost", $port = 8860) { - $ctx = ($this->insecure) ? 'tcp' : 'tls'; - $address = "{$ctx}://{$hostname}:{$port}"; - - // check setup context for TLS connection - $context = NULL; - if (!$this->insecure) { - $context = stream_context_create(); - if ($this->tls_root_certificate) stream_context_set_option($context, 'ssl', 'cafile', $this->tls_root_certificate); - if ($this->tls_certificate) stream_context_set_option($context, 'ssl', 'local_cert', $this->tls_certificate); - if ($this->tls_certificate_key) stream_context_set_option($context, 'ssl', 'local_pk', $this->tls_certificate_key); - } - - // connect to remote socket - $socket = stream_socket_client($address, $this->errcode, $this->errmsg, $this->connect_timeout, STREAM_CLIENT_CONNECT, $context); - if (!$socket) { - if ($this->errcode == 0) { - // if the value returned in errcode is 0 and stream_socket_client returned false, it is an indication - // that the error occurred before the connect() call. This is most likely due to a problem initializing - // the socket - $extmsg = ($this->insecure) ? '(before connecting to remote host)' : '(possibly wrong TLS certificate)'; - $this->errmsg = "An error occurred while initializing the socket {$extmsg}."; - $this->errcode = -1; - } - return false; - } - - $this->socket = $socket; - if ($this->internal_config_apply() == false) return false; - - return true; - } - - public function disconnect () { - $this->internal_clear_error(); - if ($this->socket) fclose($this->socket); - $this->socket = NULL; - } - - public function execute ($command) { - return $this->internal_run_command($command); - } - - public function sendblob ($blob) { - $this->isblob = true; - $rc = $this->internal_run_command($blob); - $this->isblob = false; - return $rc; - } - - // MARK: - - - // PRIVATE - - // lz4decode function from http://heap.ch/blog/2019/05/18/lz4-decompression/ - /* - MIT License - - Copyright (c) 2019 Stephan J. Müller - - 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. - */ - private function lz4decode($in, $offset = 0, $header = '') { - $len = strlen($in); - $out = $header; - $i = $offset; - $take = function() use ($in, &$i) { - return ord($in[$i++]); - }; - $addOverflow = function(&$sum) use ($take) { - do { - $sum += $summand = $take(); - } while ($summand === 0xFF); - }; - while ($i < $len) { - $token = $take(); - $nLiterals = $token >> 4; - if ($nLiterals === 0xF) $addOverflow($nLiterals); - $out .= substr($in, $i, $nLiterals); - $i += $nLiterals; - if ($i === $len) break; - $offset = $take() | $take() << 8; - $matchlength = $token & 0xF; - if ($matchlength === 0xF) $addOverflow($matchlength); - $matchlength += 4; - $j = strlen($out) - $offset; - while ($matchlength--) { - $out .= $out[$j++]; - } - } - return $out; - } - - private function internal_config_apply () { - if ($this->timeout > 0) stream_set_timeout($this->socket, $this->timeout); - - $buffer = ''; - if ((strlen($this->username) > 0) && (strlen($this->password) > 0)) { - $buffer .= "AUTH USER {$this->username} PASSWORD {$this->password};"; - } - - if (strlen($this->database) > 0) { - $buffer .= "USE DATABASE {$this->database};"; - } - - if ($this->compression) { - $buffer .= "SET CLIENT KEY COMPRESSION TO 1;"; - } - - if ($this->sqlitemode) { - $buffer .= "SET CLIENT KEY SQLITE TO 1;"; - } - - if ($this->zerotext) { - $buffer .= "SET CLIENT KEY ZEROTEXT TO 1;"; - } - - if (strlen($buffer) > 0) { - $result = $this->internal_run_command($buffer); - if ($result === false) return false; - } - - return true; - } - - private function internal_run_command ($buffer) { - $this->internal_clear_error(); - - if ($this->internal_socket_write($buffer) === false) return false; - return $this->internal_socket_read(); - } - - private function internal_setup_pubsub ($buffer) { - return true; - } - - private function internal_reconnect ($buffer) { - return true; - } - - private function internal_parse_array ($buffer) { - // extract the number of values in the array - $start = 0; - $n = $this->internal_parse_number($buffer, $start, $unused, 0); - - // loop to parse each individual value - $r = array(); - for ($i=0; $i < $n; ++$i) { - $cellsize = 0; - $len = strlen($buffer) - $start; - $value = $this->internal_parse_value($buffer, $len, $cellsize, $start); - $start += $cellsize; - array_push($r, $value); - } - - return $r; - } - - private function internal_clear_error () { - $this->errmsg = NULL; - $this->errcode = 0; - $this->xerrcode = 0; - } - - private function internal_socket_write ($buffer) { - // compute header - $delimit = ($this->isblob) ? '$' : '+'; - $len = ($buffer) ? strlen($buffer) : 0; - $header = "{$delimit}{$len} "; - - // write header and buffer - if (fwrite($this->socket, $header) === false) return false; - if ($len == 0) return true; - if (fwrite($this->socket, $buffer) === false) return false; - - return true; - } - - private function internal_socket_read () { - $buffer = ""; - $len = 8192; - - $nread = 0; - while (true) { - // read from socket - $temp = fread($this->socket, $len); - if ($temp === false) return false; - - // update buffers - $buffer .= $temp; - $nread += strlen($temp); - - // get first character - $c = $buffer[0]; - - // check if command does not have an explicit length - if (($c == CMD_INT) || ($c == CMD_FLOAT) || ($c == CMD_NULL)) { - // command is terminated by a space character - if ($buffer[$nread-1] != ' ') continue; - } else { - $cstart = 0; - $n = $this->internal_parse_number($buffer, $cstart); - - $can_be_zerolength = ($c == CMD_BLOB) || ($c == CMD_STRING); - if ($n == 0 && !$can_be_zerolength) continue; - - // check exit condition - if ($n + $cstart != $nread) continue; - } - - return $this->internal_parse_buffer($buffer, $nread); - } - - return false; - } - - private function internal_uncompress_data ($buffer, $blen) { - // %LEN COMPRESSED UNCOMPRESSED BUFFER - - $tlen = 0; // total length - $clen = 0; // compressed length - $ulen = 0; // uncompressed length - $hlen = 0; // raw header length - $seek1 = 0; - - $start = 1; - $counter = 0; - for ($i = 0; $i < $blen; $i++) { - if ($buffer[$i] != ' ') continue; - ++$counter; - - $data = substr($buffer, $start, $i-$start); - $start = $i + 1; - - if ($counter == 1) { - $tlen = intval($data); - $seek1 = $start; - } - else if ($counter == 2) { - $clen = intval($data); - } - else if ($counter == 3) { - $ulen = intval($data); - break; - } - } - - // sanity check header values - if ($tlen == 0 || $clen == 0 || $ulen == 0 || $start == 1 || $seek1 == 0) return NULL; - - // copy raw header - $hlen = $start - $seek1; - $header = substr($buffer, $start, $hlen); - - // compute index of the first compressed byte - $start += $hlen; - - // perform real decompression in pure PHP code - $clone = $this->lz4decode($buffer, $start, $header); - - // sanity check result - if (strlen($clone) != $ulen + $hlen) return NULL; - - return $clone; - } - - private function internal_parse_value ($buffer, &$len, &$cellsize = NULL, $index = 0) { - if ($len <= 0) return NULL; - - // handle special NULL value case - if (is_null($buffer) || $buffer[$index] == CMD_NULL) { - $len = 0; - if (!is_null($cellsize)) $cellsize = 2; - return NULL; - } - - $cstart = $index; - $blen = $this->internal_parse_number($buffer, $cstart, $unused, $index+1); - - // handle decimal/float cases - if (($buffer[$index] == CMD_INT) || ($buffer[$index] == CMD_FLOAT)) { - $nlen = $cstart - $index; - $len = $nlen - 2; - if (!is_null($cellsize)) $cellsize = $nlen; - return substr($buffer, $index+1, $len); - } - - $len = ($buffer[$index] == CMD_ZEROSTRING) ? $blen - 1 : $blen; - if (!is_null($cellsize)) $cellsize = $blen + $cstart - $index; - - return substr($buffer, $cstart, $len); - } - - private function internal_parse_buffer ($buffer, $blen) { - // possible return values: - // true => OK - // false => error - // integer - // double - // string - // array - // object - // NULL - - // check OK value - if (strcmp($buffer, '+2 OK') == 0) return true; - - // check for compressed result - if ($buffer[0] == CMD_COMPRESSED) { - $buffer = $this->internal_uncompress_data ($buffer, $blen); - if ($buffer == NULL) { - $this->errcode = -1; - $this->errmsg = 'An error occurred while decompressing the input buffer of len {$len}.'; - return false; - } - } - - // first character contains command type - switch ($buffer[0]) { - case CMD_ZEROSTRING: - case CMD_RECONNECT: - case CMD_PUBSUB: - case CMD_COMMAND: - case CMD_STRING: - case CMD_ARRAY: - case CMD_BLOB: - case CMD_JSON: { - $cstart = 0; - $len = $this->internal_parse_number($buffer, $cstart); - if ($len == 0) return ""; - - if ($buffer[0] == CMD_ZEROSTRING) --$len; - $clone = substr($buffer, $cstart, $len); - - if ($buffer[0] == CMD_COMMAND) return $this->internal_run_command($clone); - else if ($buffer[0] == CMD_PUBSUB) return $this->internal_setup_pubsub($clone); - else if ($buffer[0] == CMD_RECONNECT) return $this->internal_reconnect($clone); - else if ($buffer[0] == CMD_ARRAY) return $this->internal_parse_array($clone); - - return $clone; - } - - case CMD_ERROR: { - // -LEN ERRCODE:EXTCODE ERRMSG - $cstart = 0; $cstart2 = 0; - $len = $this->internal_parse_number($buffer, $cstart); - $clone = substr($buffer, $cstart); - - $extcode = 0; - $errcode = $this->internal_parse_number($clone, $cstart2, $extcode, 0); - $this->errcode = $errcode; - $this->xerrcode = $extcode; - - $len -= $cstart2; - $this->errmsg = substr($clone, $cstart2); - - return false; - } - - case CMD_ROWSET: - case CMD_ROWSET_CHUNK: { - // CMD_ROWSET: *LEN 0:VERSION ROWS COLS DATA - // CMD_ROWSET_CHUNK: /LEN IDX:VERSION ROWS COLS DATA - $start = $this->internal_parse_rowset_signature($buffer, $len, $idx, $version, $nrows, $ncols); - if ($start < 0) return false; - - // check for end-of-chunk condition - if ($start == 0 && $version == 0) { - $rowset = $this->rowset; - $this->rowset = NULL; - return $rowset; - } - - // continue parsing - return $this->internal_parse_rowset($buffer, $start, $idx, $version, $nrows, $ncols); - } - - case CMD_NULL: - return NULL; - - case CMD_INT: - case CMD_FLOAT: { - $clone = $this->internal_parse_value($buffer, $blen); - if (is_null($clone)) return 0; - if ($buffer[0] == CMD_INT) return intval($clone); - return floatval($clone); - } - - case CMD_RAWJSON: - return NULL; - } - - return NULL; - } - - private function internal_parse_number ($buffer, &$cstart, &$extcode = NULL, $index = 1) { - $value = 0; - $extvalue = 0; - $isext = false; - $blen = strlen($buffer); - - // from 1 to skip the first command type character - for ($i = $index; $i < $blen; $i++) { - $c = $buffer[$i]; - - // check for optional extended error code (ERRCODE:EXTERRCODE) - if ($c == ':') {$isext = true; continue;} - - // check for end of value - if ($c == ' ') { - $cstart = $i + 1; - if (!is_null($extcode)) $extcode = $extvalue; - return $value; - } - - // compute numeric value - if ($isext) $extvalue = ($extvalue * 10) + ((int)$buffer[$i]); - else $value = ($value * 10) + ((int)$buffer[$i]); - } - - return 0; - } - - // MARK: - - - function internal_parse_rowset_signature ($buffer, &$len, &$idx, &$version, &$nrows, &$ncols) { - // ROWSET: *LEN 0:VERS NROWS NCOLS DATA - // ROWSET in CHUNK: /LEN IDX:VERS NROWS NCOLS DATA - - // check for end-of-chunk condition - if ($buffer == '/6 0 0 0 ') { - $version = 0; - return 0; - } - - $start = 1; - $counter = 0; - $n = strlen($buffer); - for ($i = 0; $i < $n; $i++) { - if ($buffer[$i] != ' ') continue; - ++$counter; - - $data = substr($buffer, $start, $i-$start); - $start = $i + 1; - - if ($counter == 1) { - $len = intval($data); - } - else if ($counter == 2) { - // idx:vers - $values = explode(":", $data); - $idx = intval($values[0]); - $version = intval($values[1]); - } - else if ($counter == 3) { - $nrows = intval($data); - } - else if ($counter == 4) { - $ncols = intval($data); - return $start; - } - else return -1; - } - return -1; - } - - function internal_parse_rowset_header ($rowset, $buffer, $start) { - $ncols = $rowset->ncols; - - // parse column names - $rowset->colname = array(); - for ($i = 0; $i < $ncols; $i++) { - $len = $this->internal_parse_number($buffer, $cstart, $unused, $start); - $value = substr($buffer, $cstart, $len); - array_push($rowset->colname, $value); - $start = $cstart + $len; - } - - if ($rowset->version == 1) return $start; - - // if version != 2 returns an error - if ($rowset->version != 2) return -1; - - // parse declared types - $rowset->decltype = array(); - for ($i = 0; $i < $ncols; $i++) { - $len = $this->internal_parse_number($buffer, $cstart, $unused, $start); - $value = substr($buffer, $cstart, $len); - array_push($rowset->decltype, $value); - $start = $cstart + $len; - } - - // parse database names - $rowset->dbname = array(); - for ($i = 0; $i < $ncols; $i++) { - $len = $this->internal_parse_number($buffer, $cstart, $unused, $start); - $value = substr($buffer, $cstart, $len); - array_push($rowset->dbname, $value); - $start = $cstart + $len; - } - - // parse table names - $rowset->tblname = array(); - for ($i = 0; $i < $ncols; $i++) { - $len = $this->internal_parse_number($buffer, $cstart, $unused, $start); - $value = substr($buffer, $cstart, $len); - array_push($rowset->tblname, $value); - $start = $cstart + $len; - } - - // parse column original names - $rowset->origname = array(); - for ($i = 0; $i < $ncols; $i++) { - $len = $this->internal_parse_number($buffer, $cstart, $unused, $start); - $value = substr($buffer, $cstart, $len); - array_push($rowset->origname, $value); - $start = $cstart + $len; - } - - return $start; - } - - function internal_parse_rowset_values ($rowset, $buffer, $start, $bound) { - // loop to parse each individual value - for ($i=0; $i < $bound; ++$i) { - $cellsize = 0; - $len = strlen($buffer) - $start; - $value = $this->internal_parse_value($buffer, $len, $cellsize, $start); - $start += $cellsize; - array_push($rowset->data, $value); - } - } - - function internal_parse_rowset($buffer, $start, $idx, $version, $nrows, $ncols) { - $rowset = NULL; - $n = $start; - $ischunk = ($buffer[0] == CMD_ROWSET_CHUNK); - - // idx == 0 means first (and only) chunk for rowset - // idx == 1 means first chunk for chunked rowset - $first_chunk = ($ischunk) ? ($idx == 1) : ($idx == 0); - if ($first_chunk) { - $rowset = new SQLiteCloudRowset(); - $rowset->nrows = $nrows; - $rowset->ncols = $ncols; - $rowset->version = $version; - $rowset->data = array(); - if ($ischunk) $this->rowset = $rowset; - $n = $this->internal_parse_rowset_header($rowset, $buffer, $start); - if ($n <= 0) return NULL; - } else { - $rowset = $this->rowset; - $rowset->nrows += $nrows; - } - - // parse values - $this->internal_parse_rowset_values($rowset, $buffer, $n, $nrows*$ncols); - - if ($ischunk) { - if ($this->internal_socket_write("OK") === false) { - $this->rowset = NULL; - return false; - } - return $this->internal_socket_read(); - } - - return $rowset; - } - - // MARK: - - - function __destruct() { - $this->disconnect(); - } - } - -?> \ No newline at end of file diff --git a/PROTOCOL.md b/PROTOCOL.md index de1609c5..44e26b6e 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -18,7 +18,7 @@ A client connects to an SQLite Cloud server creating a TCP connection to port 88 ### Request-Response model SQLite Cloud accepts strings representing the command to execute. These strings are parsed on the server-side and the command-specific reply is sent back to the client. A client is allowed to send to the server only **SCSP Strings**. -Multiple commands can be sent to the SQLite Cloud server as a one **SCPS String** with commands separated by the `;` character. In that case, the client will receive one reply representing the response of the last executed command. In case of error, the execution is interrupted, and the proper error code is returned. +Multiple commands can be sent to the SQLite Cloud server as a one **SCSP String** with commands separated by the `;` character. In that case, the client will receive one reply representing the response of the last executed command. In case of error, the execution is interrupted, and the proper error code is returned. In SCSP, the type of data depends on the first byte: * For **Strings** the first byte is `+` @@ -50,18 +50,16 @@ The format is `+LEN STRING`. The whole command is built by four parts: For example to send the string "Hello World!" the command would be: `+12 Hello World!` ### SCSP Zero-Terminated Strings -The format is `!LEN STRING0`. See **SCSP Strings** for details, the only difference is that `STRING` is sent with a 0 at the end (for better performance in C-like environment that requires Zero-Terminated strings). The LEN field includes the 0 at the end. +The format is `!LEN STRING0`. See **SCSP Strings** for details. The only difference is that `STRING` is sent with a 0 at the end, represented in hexadecimal (`\x00`), for better performance in C-like environments that require zero-terminated strings. The LEN field includes the \x00 at the end. -For example to send the string "Hello World!" the command would be: `!13 Hello World!0` +For example, to send the string "Hello World!" the command would be: `!13 Hello World!\x00`. ### SCSP Errors -The format is `-LEN ERRCODE STRING`. Error replies are only sent by the server when something wrong happens. The first ERRCODE field in the error represents a numeric error code. The remaining string is the error message itself. The error code is useful for clients to distinguish among different error conditions without having to do pattern matching in the error message, that may change. LEN does not include the length of the first `-LEN ` part. +The format is `-LEN ERRCODE[:EXTCODE[:OFFCODE]] STRING`. Error replies are only sent by the server when something wrong happens. The ERRCODE field in the numeric error code. The optional part, EXTCODE and OFFCODE are numeric values specific to SQLite. EXTCODE represents the extended error code (as specified in the documentation at https://www.sqlite.org/rescode.html), while OFFCODE indicates the offset index within the SQL token where the syntax error occurs (or -1 if none). TThe remaining part of the string corresponds to the error message itself. The error code proves valuable to clients as it enables them to differentiate between various error conditions without resorting to pattern matching within the error message, which may undergo changes. LEN does not include the length of the first `-LEN ` part. -If `ERRCODE` contains an extended error code (optional) then the `ERRCODE` format is `ERRCODE:EXTERRCODE`. - -- ERROCODE < 10,000 are SQLite error codes as reported by https://www.sqlite.org/rescode.html +- ERRCODE < 10,000 are SQLite error codes as reported by https://www.sqlite.org/rescode.html - ERRCODE >= 10,000 and <100,000 are SQLite Cloud error codes -- ERROCODE >= 100,000 are generated internally by the SDK +- ERRCODE >= 100,000 are generated internally by the SDK ### SCSP Integer The format is `:VALUE `. Where `VALUE` is a string representation of the integer value. `VALUE` can be negative and in C it can be parsed using the `strtol/strtoll` API. `VALUE` is guarantee to be an Integer 64 bit number. @@ -70,12 +68,14 @@ The format is `:VALUE `. Where `VALUE` is a string representation of the integer The format is `,VALUE `. Where `VALUE` is a string representation of the float/double value. In C, `VALUE` can be parsed using the `strtod` API. In this first implementation `VALUE` is guarantee to be a Double number. ### SCSP **Rowset** -The format is `*LEN 0:VERS NROWS NCOLS DATA`. The whole command is built by eight parts: +The format is `*LEN 0:VERS NROWS NCOLS DATA`. The whole command is built by ten parts: 1. The single `*` character 2. LEN is a string representation of Rowset length (theoretically the maximum supported value is UINT64_MAX but it is usually much lower. LEN does not include the length of the first `*LEN ` part. 3. A single space is used to separate LEN from 0:VERS -4. a single `0:` string followed by a VERS number (a string representation of the number) which specifies the version of the Rowset (`1` means that only column names is included in the header, `2` means that column names, declared types, database names, table names and origin names are included in the header) `MORE ABOUT THE VERSION MEANING` +4. a single `0:` string followed by a VERS number (a string representation of the number) which specifies the version of the Rowset. + * `1`: means that only column names are included in the header + * `2`: means that column names, declared types, database names, table names, origin names, not null flags, primary key flags and autoincrement flags are included in the header (one value for each column) 5. A single space is used to separate 0:VERS from NROWS 6. NROWS is a string representation of the number of rows contained in the Rowset (can be zero) 7. A single space is used to separate NROWS from NCOLS @@ -95,7 +95,8 @@ The format is `/LEN IDX:VERS NROWS NCOLS DATA`. The command is equal to the SCSP 3. NROWS represents the number of rows contained in the chunk. The total number of rows in the final Rowset will be the sum of each NROWS contained in each chunk 4. NCOLS will be the same for all chunks, which means that it does not need to be computed (as a sum) in the final Rowset, and it means that a logical line is never break 5. To mark the end of the Rowset, the special string `/LEN 0 0 0 ` is sent (LEN is always 6 in this case) -6. After receiving a chuck the client must send an ACK message `+2 OK` to the server (to notify that it is ready to receive the next chunk). Any other ACK message is considered an ABORT and the Rowset processing is immediately aborted. + +When the Rowset is sent in chuck, it is guaranteed that the first chuck contains a complete header and that all the chunks contain complete rows (the individual fields are not truncated in any way). ### SCSP RAW JSON When the first character is `{` that means that the whole packet is guarantee to be a valid JSON value that can be parsed with a JSON parser. @@ -118,7 +119,7 @@ Any value can be compressed and the format is `%LEN COMPRESSED UNCOMPRESSED BUFF 3. A single space is used to separate LEN from COMPRESSED 4. COMPRESSED is a string representation of the compressed BUFFER size 5. UNCOMPRESSED is a string representation of the uncompressed BUFFER size -6. BUFFER can be any SCSP value +6. BUFFER can be any SCSP value containing a UNCOMPRESSED SCSP header followed by LZ4 compressed data. At the time of this writing, the only supported compressed SCSP value is the Rowset. In the chunked version, the `/LEN` header is replaced by `/0` (for performance optimizations on the server side). The data `LEN` value is equivalent to the `UNCOMPRESSED` value of the initial compression header. ### SCSP Command The format is `^LEN COMMAND`. The whole command is built by four parts: @@ -142,5 +143,18 @@ The format is `=LEN N VALUE1 VALUE2 ... VALUEN`. The whole command is built by N 3. N is the number of items in the array 4. N values separated by a space ` ` character +### SQLite Statements +SQLite statements are usually sent from client to server as `SCSP Strings`. +In case of bindings the whole statement can be sent as `SCSP Array`. + +The server replies to READ statements (like SELECT) with a `SCSP Rowset` or SCSP Rowset Chunk. In case of WRITE statements (like INSERT, UPDATE, DELETE) the SQLite Cloud server replies with a `SCSP Array` in the following format: `=LEN 6 TYPE INDEX ROWID CHANGES TOTAL_CHANGES FINALIZED`: + +1. TYPE is always 10 in this case +2. INDEX is always 0 in this case +3. ROWID is the result of the [sqlite3_last_insert_rowid](https://www.sqlite.org/c3ref/last_insert_rowid.html) function +4. CHANGES is the result of the [sqlite3_changes64](https://www.sqlite.org/c3ref/changes.html) function +5. TOTAL_CHANGES is the result of the [sqlite3_total_changes64](https://www.sqlite.org/c3ref/total_changes.html) function +6. FINALIZED is always 1 in this case + --- -```Last revision: April 13th, 2022``` +```Last revision: January 24th, 2024``` diff --git a/sqlitecloud-go b/sqlitecloud-go new file mode 160000 index 00000000..7c0b00df --- /dev/null +++ b/sqlitecloud-go @@ -0,0 +1 @@ +Subproject commit 7c0b00dfc0777c3892b54e36cd9b21273a55ed12 diff --git a/sqlitecloud-js b/sqlitecloud-js new file mode 160000 index 00000000..507d204b --- /dev/null +++ b/sqlitecloud-js @@ -0,0 +1 @@ +Subproject commit 507d204bbd4edf5288741bb17810443e4c5b0592 diff --git a/sqlitecloud-php b/sqlitecloud-php new file mode 160000 index 00000000..8bbe9549 --- /dev/null +++ b/sqlitecloud-php @@ -0,0 +1 @@ +Subproject commit 8bbe9549fe9fda04b69e127651c6082de962e69b diff --git a/sqlitecloud-py b/sqlitecloud-py new file mode 160000 index 00000000..bca535a2 --- /dev/null +++ b/sqlitecloud-py @@ -0,0 +1 @@ +Subproject commit bca535a25a2d2d30894bd1dc774eedbf9b3ccba8