commit 834b19a68a9478967f270a855024dc7fa233cc8d Author: Andrewkydev Date: Thu Jan 15 22:36:39 2026 +0300 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..584ebfa --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +.gradle +build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +# End of https://mrkandreev.name/snippets/gitignore-generator/#Java,Gradle \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c7d9806 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# Full Example Plugin + +Standalone example plugin that uses the Database API. + +## Build + +1. Publish Database plugin to local Maven: + +```powershell +./gradlew publishToMavenLocal +``` + +2. Build this example: + +```powershell +./gradlew build +``` + +3. Drop the jar into your server `plugins` folder. + +## Notes + +- This plugin depends on the `Database` plugin at runtime. +- Check `plugin.yml` for `depend: [ Database ]`. +- For full coverage, toggle `RUN_ADVANCED` (JOIN/GROUP BY/HAVING) and `RUN_DESTRUCTIVE` (create/drop database and demo tables) in `examples/FullPlugin/src/main/java/com/andrewkydev/example/full/run/ExampleCoordinator.java`. diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..45498cd --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + id("java") +} + +group = "com.andrewkydev" +version = "0.0.1" + +repositories { + mavenLocal() + mavenCentral() + + maven { + name = "luminiadevRepositorySnapshots" + url = uri("https://repo.luminiadev.com/snapshots") + } +} + +dependencies { + compileOnly("com.koshakmine:Lumi:1.4.0-SNAPSHOT") + compileOnly("com.andrewkydev:Database:1.0-SNAPSHOT") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..1b33c55 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ca025c8 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..23d15a9 --- /dev/null +++ b/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..db3a6ac --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..e6c5b50 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "DatabaseFullExample" diff --git a/src/main/java/com/andrewkydev/example/ExamplePlugin.java b/src/main/java/com/andrewkydev/example/ExamplePlugin.java new file mode 100644 index 0000000..83e2174 --- /dev/null +++ b/src/main/java/com/andrewkydev/example/ExamplePlugin.java @@ -0,0 +1,12 @@ +package com.andrewkydev.example; + +import cn.nukkit.plugin.PluginBase; +import com.andrewkydev.example.run.ExampleCoordinator; + +public class ExamplePlugin extends PluginBase { + + @Override + public void onEnable() { + ExampleCoordinator.run(this); + } +} diff --git a/src/main/java/com/andrewkydev/example/examples/AsyncExamples.java b/src/main/java/com/andrewkydev/example/examples/AsyncExamples.java new file mode 100644 index 0000000..b6be47e --- /dev/null +++ b/src/main/java/com/andrewkydev/example/examples/AsyncExamples.java @@ -0,0 +1,122 @@ +package com.andrewkydev.example.examples; + +import cn.nukkit.plugin.PluginBase; +import com.andrewkydev.database.DatabaseApi; +import com.andrewkydev.database.query.RowMapper; +import com.andrewkydev.example.model.PlayerModel; + +import java.util.Collections; +import java.util.UUID; + +public final class AsyncExamples { + private AsyncExamples() { + } + + public static void run(PluginBase plugin, DatabaseApi api) { + api.query().executeAsync("UPDATE players SET level = level") + .thenAccept(rows -> plugin.getLogger().info("Async update all rows: " + rows)); + + api.query().executeAsync( + "UPDATE players SET level = level + 1 WHERE username = ?", + Collections.singletonList("Steve") + ) + .thenAccept(rows -> plugin.getLogger().info("Async update rows: " + rows)); + + api.orm().findAllAsync(PlayerModel.class) + .thenAccept(players -> plugin.getLogger().info("Async players: " + players.size())); + + api.orm().findByIdAsync(PlayerModel.class, 1L) + .thenAccept(player -> plugin.getLogger().info("Async findById: " + (player == null ? "null" : player.getUsername()))); + + api.orm().findWhereAsync(PlayerModel.class, "level >= ?", Collections.singletonList(1)) + .thenAccept(players -> plugin.getLogger().info("Async findWhere: " + players.size())); + + api.orm().findOneWhereAsync(PlayerModel.class, "username = ?", Collections.singletonList("Steve")) + .thenAccept(player -> plugin.getLogger().info("Async findOneWhere: " + (player == null ? "null" : player.getUsername()))); + + api.orm().findWhereAsync( + PlayerModel.class, + "level >= ?", + Collections.singletonList(1), + "level DESC", + 5, + 0 + ) + .thenAccept(players -> plugin.getLogger().info("Async findWhere paged: " + players.size())); + + api.orm().countAsync(PlayerModel.class) + .thenAccept(count -> plugin.getLogger().info("Async count: " + count)); + + api.orm().existsAsync(PlayerModel.class, "username = ?", Collections.singletonList("Steve")) + .thenAccept(exists -> plugin.getLogger().info("Async exists: " + exists)); + + PlayerModel asyncPlayer = new PlayerModel(); + asyncPlayer.setUsername("AsyncUser_" + UUID.randomUUID().toString().substring(0, 8)); + asyncPlayer.setLevel(1); + api.orm().insertAsync(asyncPlayer) + .thenCompose(v -> api.orm().updateAsync(asyncPlayer)) + .thenCompose(v -> api.orm().deleteAsync(asyncPlayer)) + .thenRun(() -> plugin.getLogger().info("Async insert/update/delete ok")); + + api.orm().deleteWhereAsync(PlayerModel.class, "level < ?", Collections.singletonList(0)) + .thenAccept(rows -> plugin.getLogger().info("Async deleteWhere: " + rows)); + + api.query().queryAsync( + "SELECT username FROM players", + rs -> rs.getString("username") + ) + .thenAccept(names -> plugin.getLogger().info("Async query names: " + names.size())); + + api.query().queryAsync( + "SELECT username FROM players WHERE level >= ?", + Collections.singletonList(1), + rs -> rs.getString("username") + ) + .thenAccept(names -> plugin.getLogger().info("Async query names (params): " + names.size())); + + api.schema().addColumnAsync( + "players", + com.andrewkydev.database.schema.ColumnSpec.builder("nickname", "VARCHAR(32)").nullable(true).build() + ) + .thenRun(() -> plugin.getLogger().info("Async add column done")) + .exceptionally(ex -> { + plugin.getLogger().warning("Async add column failed: " + ex.getMessage()); + return null; + }); + + api.beginTransactionAsync().thenAccept(tx -> { + tx.execute("UPDATE players SET level = level + 1 WHERE username = ?", Collections.singletonList("Steve")); + tx.commitAsync().thenRun(tx::close); + }); + + api.beginTransactionAsync().thenAccept(tx -> { + tx.execute("UPDATE players SET level = level + 1 WHERE username = ?", Collections.singletonList("Alex")); + tx.rollbackAsync().thenRun(tx::close); + }); + + api.orm().query(PlayerModel.class) + .where("level >= ?", 1) + .listAsync() + .thenAccept(players -> plugin.getLogger().info("Async query builder list: " + players.size())); + + api.orm().query(PlayerModel.class) + .where("username = ?", "Steve") + .oneAsync() + .thenAccept(player -> plugin.getLogger().info("Async query builder one: " + (player == null ? "null" : player.getUsername()))); + + api.orm().query(PlayerModel.class) + .where("level >= ?", 1) + .countAsync() + .thenAccept(count -> plugin.getLogger().info("Async query builder count: " + count)); + + api.orm().query(PlayerModel.class) + .where("username = ?", "Steve") + .existsAsync() + .thenAccept(exists -> plugin.getLogger().info("Async query builder exists: " + exists)); + + api.orm().query(PlayerModel.class) + .where("level < ?", 0) + .deleteAsync() + .thenAccept(rows -> plugin.getLogger().info("Async query builder delete: " + rows)); + } +} diff --git a/src/main/java/com/andrewkydev/example/examples/OrmExamples.java b/src/main/java/com/andrewkydev/example/examples/OrmExamples.java new file mode 100644 index 0000000..9a8df69 --- /dev/null +++ b/src/main/java/com/andrewkydev/example/examples/OrmExamples.java @@ -0,0 +1,88 @@ +package com.andrewkydev.example.examples; + +import cn.nukkit.plugin.PluginBase; +import com.andrewkydev.database.orm.Conditions; +import com.andrewkydev.database.orm.EntityManager; +import com.andrewkydev.example.model.PlayerModel; + +import java.util.Collections; +import java.util.List; + +public final class OrmExamples { + private OrmExamples() { + } + + public static void run(PluginBase plugin, EntityManager orm) { + List all = orm.findAll(PlayerModel.class); + plugin.getLogger().info("All players: " + all.size()); + + PlayerModel byId = orm.findById(PlayerModel.class, 1L); + plugin.getLogger().info("Find by id: " + (byId == null ? "null" : byId.getUsername())); + + PlayerModel one = orm.findOneWhere( + PlayerModel.class, + "username = ?", + Collections.singletonList("Steve") + ); + plugin.getLogger().info("Find one: " + (one == null ? "null" : one.getUsername())); + + List paged = orm.findWhere( + PlayerModel.class, + "level >= ?", + Collections.singletonList(1), + "level DESC", + 5, + 0 + ); + plugin.getLogger().info("Paged players: " + paged.size()); + + long count = orm.count(PlayerModel.class, "level >= ?", Collections.singletonList(1)); + long total = orm.count(PlayerModel.class); + boolean exists = orm.exists(PlayerModel.class, "username = ?", Collections.singletonList("Steve")); + plugin.getLogger().info("Count >= 1: " + count + ", Total: " + total + ", Steve exists: " + exists); + + List byCondition = orm.query(PlayerModel.class) + .where(Conditions.eq("level", 10).or(Conditions.gt("level", 1))) + .list(); + plugin.getLogger().info("Condition list: " + byCondition.size()); + + List byChainedCondition = orm.query(PlayerModel.class) + .where(Conditions.eq("level", 1)) + .and(Conditions.like("username", "S%")) + .list(); + plugin.getLogger().info("Chained condition list: " + byChainedCondition.size()); + + List top = orm.query(PlayerModel.class) + .where("level >= ?", 1) + .orderBy("level DESC") + .limit(10) + .list(); + plugin.getLogger().info("Top players: " + top.size()); + + int deleted = orm.deleteWhere(PlayerModel.class, "level < ?", Collections.singletonList(0)); + plugin.getLogger().info("Deleted low level players: " + deleted); + + PlayerModel temp = new PlayerModel(); + temp.setUsername("TempUser"); + temp.setLevel(1); + orm.insert(temp); + temp.setLevel(2); + orm.update(temp); + orm.delete(temp); + + PlayerModel fromQuery = orm.query(PlayerModel.class) + .where("username = ?", "Steve") + .one(); + plugin.getLogger().info("Query one: " + (fromQuery == null ? "null" : fromQuery.getUsername())); + + boolean queryExists = orm.query(PlayerModel.class) + .where(Conditions.in("username", "Steve", "Alex")) + .exists(); + plugin.getLogger().info("Query exists: " + queryExists); + + int queryDeleted = orm.query(PlayerModel.class) + .where("level < ?", 0) + .delete(); + plugin.getLogger().info("Query delete: " + queryDeleted); + } +} diff --git a/src/main/java/com/andrewkydev/example/examples/QueryBuilderExamples.java b/src/main/java/com/andrewkydev/example/examples/QueryBuilderExamples.java new file mode 100644 index 0000000..9b5cbd8 --- /dev/null +++ b/src/main/java/com/andrewkydev/example/examples/QueryBuilderExamples.java @@ -0,0 +1,31 @@ +package com.andrewkydev.example.examples; + +import cn.nukkit.plugin.PluginBase; +import com.andrewkydev.database.orm.Conditions; +import com.andrewkydev.database.orm.EntityManager; +import com.andrewkydev.example.model.PlayerModel; + +import java.util.List; + +public final class QueryBuilderExamples { + private QueryBuilderExamples() { + } + + public static void run(PluginBase plugin, EntityManager orm) { + List joined = orm.query(PlayerModel.class) + .select("players.*") + .join("LEFT JOIN clans ON clans.id = players.clan_id") + .where(Conditions.like("players.username", "S%")) + .groupBy("players.id") + .having("COUNT(clans.id) >= ?", 1) + .orderBy("players.level DESC") + .limit(10, 0) + .list(); + plugin.getLogger().info("Joined rows: " + joined.size()); + + long count = orm.query(PlayerModel.class) + .where(Conditions.in("players.username", "Steve", "Alex")) + .count(); + plugin.getLogger().info("IN count: " + count); + } +} diff --git a/src/main/java/com/andrewkydev/example/examples/QueryExamples.java b/src/main/java/com/andrewkydev/example/examples/QueryExamples.java new file mode 100644 index 0000000..9ce1495 --- /dev/null +++ b/src/main/java/com/andrewkydev/example/examples/QueryExamples.java @@ -0,0 +1,35 @@ +package com.andrewkydev.example.examples; + +import cn.nukkit.plugin.PluginBase; +import com.andrewkydev.database.query.QueryRunner; +import com.andrewkydev.database.query.RowMapper; + +import java.util.Collections; +import java.util.List; + +public final class QueryExamples { + private QueryExamples() { + } + + public static void run(PluginBase plugin, QueryRunner query) { + query.execute("UPDATE players SET level = level"); + int updated = query.execute( + "UPDATE players SET level = level + 1 WHERE username = ?", + Collections.singletonList("Steve") + ); + plugin.getLogger().info("Rows updated: " + updated); + + List names = query.query( + "SELECT username FROM players WHERE level >= ?", + Collections.singletonList(1), + rs -> rs.getString("username") + ); + plugin.getLogger().info("Names size: " + names.size()); + + List levels = query.query( + "SELECT level FROM players", + rs -> rs.getInt("level") + ); + plugin.getLogger().info("Levels size: " + levels.size()); + } +} diff --git a/src/main/java/com/andrewkydev/example/examples/SchemaExamples.java b/src/main/java/com/andrewkydev/example/examples/SchemaExamples.java new file mode 100644 index 0000000..cfdb88c --- /dev/null +++ b/src/main/java/com/andrewkydev/example/examples/SchemaExamples.java @@ -0,0 +1,59 @@ +package com.andrewkydev.example.examples; + +import cn.nukkit.plugin.PluginBase; +import com.andrewkydev.database.DatabaseApi; +import com.andrewkydev.database.orm.OrmSchema; +import com.andrewkydev.database.schema.ColumnSpec; +import com.andrewkydev.database.schema.IndexSpec; +import com.andrewkydev.database.schema.SqlDialect; +import com.andrewkydev.database.schema.TableSpec; +import com.andrewkydev.example.model.PlayerModel; + +import java.util.Collections; + +public final class SchemaExamples { + private SchemaExamples() { + } + + public static void setup(PluginBase plugin, DatabaseApi api) { + TableSpec spec = OrmSchema.fromEntity(PlayerModel.class, SqlDialect.MYSQL); + try { + api.schema().createTable(spec); + } catch (Exception ex) { + plugin.getLogger().warning("Table create failed (might already exist): " + ex.getMessage()); + } + } + + public static void runDestructive(PluginBase plugin, DatabaseApi api) { + api.schema().createDatabase("lumi_demo"); + api.schema().dropDatabase("lumi_demo"); + + api.schema().createDatabaseAsync("lumi_demo_async") + .thenRun(() -> api.schema().dropDatabaseAsync("lumi_demo_async")); + + TableSpec demo = TableSpec.builder("players_demo") + .column(ColumnSpec.builder("id", "BIGINT").primaryKey(true).autoIncrement(true).nullable(false).build()) + .column(ColumnSpec.builder("username", "VARCHAR(32)").nullable(false).build()) + .build(); + + api.schema().createTable(demo); + api.schema().addColumn("players_demo", ColumnSpec.builder("level", "INT").nullable(true).build()); + api.schema().updateColumn("players_demo", ColumnSpec.builder("level", "INT").nullable(false).build()); + api.schema().addIndex("players_demo", new IndexSpec("players_demo_username_idx", Collections.singletonList("username"), false)); + api.schema().dropIndex("players_demo", "players_demo_username_idx"); + api.schema().dropColumn("players_demo", "level"); + api.schema().dropTable("players_demo"); + + TableSpec demoAsync = TableSpec.builder("players_demo_async") + .column(ColumnSpec.builder("id", "BIGINT").primaryKey(true).autoIncrement(true).nullable(false).build()) + .column(ColumnSpec.builder("username", "VARCHAR(32)").nullable(false).build()) + .build(); + + api.schema().createTableAsync(demoAsync) + .thenCompose(v -> api.schema().dropTableAsync("players_demo_async")) + .exceptionally(ex -> { + plugin.getLogger().warning("Async schema failed: " + ex.getMessage()); + return null; + }); + } +} diff --git a/src/main/java/com/andrewkydev/example/examples/SeedData.java b/src/main/java/com/andrewkydev/example/examples/SeedData.java new file mode 100644 index 0000000..e970ae3 --- /dev/null +++ b/src/main/java/com/andrewkydev/example/examples/SeedData.java @@ -0,0 +1,35 @@ +package com.andrewkydev.example.examples; + +import cn.nukkit.plugin.PluginBase; +import com.andrewkydev.database.orm.EntityManager; +import com.andrewkydev.example.model.PlayerModel; + +import java.util.Collections; + +public final class SeedData { + private SeedData() { + } + + public static void seed(PluginBase plugin, EntityManager orm) { + PlayerModel player = orm.findOneWhere( + PlayerModel.class, + "username = ?", + Collections.singletonList("Steve") + ); + if (player != null) { + return; + } + + PlayerModel steve = new PlayerModel(); + steve.setUsername("Steve"); + steve.setLevel(10); + orm.insert(steve); + + PlayerModel alex = new PlayerModel(); + alex.setUsername("Alex"); + alex.setLevel(5); + orm.insert(alex); + + plugin.getLogger().info("Seeded demo players"); + } +} diff --git a/src/main/java/com/andrewkydev/example/examples/TransactionExamples.java b/src/main/java/com/andrewkydev/example/examples/TransactionExamples.java new file mode 100644 index 0000000..9bb47ae --- /dev/null +++ b/src/main/java/com/andrewkydev/example/examples/TransactionExamples.java @@ -0,0 +1,44 @@ +package com.andrewkydev.example.examples; + +import cn.nukkit.plugin.PluginBase; +import com.andrewkydev.database.DatabaseApi; +import com.andrewkydev.database.query.RowMapper; +import com.andrewkydev.database.query.Transaction; + +import java.util.Collections; +import java.util.List; + +public final class TransactionExamples { + private TransactionExamples() { + } + + public static void run(PluginBase plugin, DatabaseApi api) { + try (Transaction tx = api.beginTransaction()) { + tx.execute( + "UPDATE players SET level = level + 1 WHERE username = ?", + Collections.singletonList("Alex") + ); + List names = tx.query( + "SELECT username FROM players WHERE level >= ?", + Collections.singletonList(1), + rs -> rs.getString("username") + ); + plugin.getLogger().info("Tx names: " + names.size()); + tx.commit(); + plugin.getLogger().info("Transaction committed"); + } catch (Exception ex) { + plugin.getLogger().warning("Transaction failed: " + ex.getMessage()); + } + + try (Transaction tx = api.beginTransaction()) { + tx.execute( + "UPDATE players SET level = level + 1 WHERE username = ?", + Collections.singletonList("Steve") + ); + tx.rollback(); + plugin.getLogger().info("Transaction rolled back"); + } catch (Exception ex) { + plugin.getLogger().warning("Rollback failed: " + ex.getMessage()); + } + } +} diff --git a/src/main/java/com/andrewkydev/example/model/PlayerModel.java b/src/main/java/com/andrewkydev/example/model/PlayerModel.java new file mode 100644 index 0000000..73c0439 --- /dev/null +++ b/src/main/java/com/andrewkydev/example/model/PlayerModel.java @@ -0,0 +1,77 @@ +package com.andrewkydev.example.model; + +import com.andrewkydev.database.orm.DbColumn; +import com.andrewkydev.database.orm.DbEntity; +import com.andrewkydev.database.orm.DbId; +import com.andrewkydev.database.orm.DbJson; + +import java.util.Map; +import java.util.UUID; + +@DbEntity(table = "players") +public class PlayerModel { + @DbId(autoIncrement = true) + private long id; + + @DbColumn(nullable = false, unique = true, length = 32) + private String username; + + @DbColumn(nullable = false) + private int level; + + @DbColumn(type = "VARCHAR(16)") + private Rank rank = Rank.MEMBER; + + @DbColumn(type = "CHAR(36)") + private UUID externalId = UUID.randomUUID(); + + @DbJson + private Map metadata; + + public PlayerModel() { + } + + public long getId() { + return id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public int getLevel() { + return level; + } + + public void setLevel(int level) { + this.level = level; + } + + public Rank getRank() { + return rank; + } + + public void setRank(Rank rank) { + this.rank = rank; + } + + public UUID getExternalId() { + return externalId; + } + + public void setExternalId(UUID externalId) { + this.externalId = externalId; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } +} diff --git a/src/main/java/com/andrewkydev/example/model/Rank.java b/src/main/java/com/andrewkydev/example/model/Rank.java new file mode 100644 index 0000000..0896702 --- /dev/null +++ b/src/main/java/com/andrewkydev/example/model/Rank.java @@ -0,0 +1,6 @@ +package com.andrewkydev.example.model; + +public enum Rank { + MEMBER, + VIP +} diff --git a/src/main/java/com/andrewkydev/example/orm/AdapterRegistrar.java b/src/main/java/com/andrewkydev/example/orm/AdapterRegistrar.java new file mode 100644 index 0000000..1f71210 --- /dev/null +++ b/src/main/java/com/andrewkydev/example/orm/AdapterRegistrar.java @@ -0,0 +1,27 @@ +package com.andrewkydev.example.orm; + +import com.andrewkydev.database.orm.EntityManager; +import com.andrewkydev.database.orm.TypeAdapter; +import com.andrewkydev.example.model.Rank; + +public final class AdapterRegistrar { + private AdapterRegistrar() { + } + + public static void register(EntityManager orm) { + orm.registerAdapter(Rank.class, new TypeAdapter() { + @Override + public Object toDatabase(Rank value) { + return value == null ? null : value.name(); + } + + @Override + public Rank fromDatabase(Object value) { + if (value == null) { + return null; + } + return Rank.valueOf(value.toString()); + } + }); + } +} diff --git a/src/main/java/com/andrewkydev/example/run/ExampleCoordinator.java b/src/main/java/com/andrewkydev/example/run/ExampleCoordinator.java new file mode 100644 index 0000000..10e1e0a --- /dev/null +++ b/src/main/java/com/andrewkydev/example/run/ExampleCoordinator.java @@ -0,0 +1,36 @@ +package com.andrewkydev.example.run; + +import cn.nukkit.plugin.PluginBase; +import com.andrewkydev.database.DatabaseApi; +import com.andrewkydev.database.DatabaseProvider; +import com.andrewkydev.database.orm.EntityManager; +import com.andrewkydev.example.examples.*; +import com.andrewkydev.example.orm.AdapterRegistrar; + +public final class ExampleCoordinator { + private static final boolean RUN_DESTRUCTIVE = false; + private static final boolean RUN_ADVANCED = false; + + private ExampleCoordinator() { + } + + public static void run(PluginBase plugin) { + DatabaseApi api = DatabaseProvider.get(); + EntityManager orm = api.orm(); + AdapterRegistrar.register(orm); + + SchemaExamples.setup(plugin, api); + SeedData.seed(plugin, orm); + + OrmExamples.run(plugin, orm); + QueryExamples.run(plugin, api.query()); + TransactionExamples.run(plugin, api); + AsyncExamples.run(plugin, api); + if (RUN_ADVANCED) { + QueryBuilderExamples.run(plugin, orm); + } + if (RUN_DESTRUCTIVE) { + SchemaExamples.runDestructive(plugin, api); + } + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..3d1f67a --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,6 @@ +name: DatabaseFullExample +description: "Full example plugin using Database API" +main: com.andrewkydev.example.full.ExamplePlugin +version: "0.0.1" +api: [ 1.1.0 ] +depend: [ Database ]