From 9f0d3ce04ce906f381f4b3db8636e9c1ad9bcf11 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:33:17 -0700 Subject: [PATCH 01/44] Test 1: User Agent --- tools/ci/download_byond.sh | 2 +- tools/ci/install_byond.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ci/download_byond.sh b/tools/ci/download_byond.sh index 19b7f201707..b845f0d2cfe 100755 --- a/tools/ci/download_byond.sh +++ b/tools/ci/download_byond.sh @@ -2,4 +2,4 @@ set -e source dependencies.sh echo "Downloading BYOND version $BYOND_MAJOR.$BYOND_MINOR" -curl "http://www.byond.com/download/build/$BYOND_MAJOR/$BYOND_MAJOR.${BYOND_MINOR}_byond.zip" -o C:/byond.zip +curl -H "User-Agent: Eris CI Script V1.2" "http://www.byond.com/download/build/$BYOND_MAJOR/$BYOND_MAJOR.${BYOND_MINOR}_byond.zip" -o C:/byond.zip diff --git a/tools/ci/install_byond.sh b/tools/ci/install_byond.sh index 4a688755d3d..7901f750605 100755 --- a/tools/ci/install_byond.sh +++ b/tools/ci/install_byond.sh @@ -11,7 +11,7 @@ else rm -rf "$HOME/BYOND" mkdir -p "$HOME/BYOND" cd "$HOME/BYOND" - curl "http://www.byond.com/download/build/${BYOND_MAJOR}/${BYOND_MAJOR}.${BYOND_MINOR}_byond_linux.zip" -o byond.zip + curl -H "User-Agent: Eris CI Script V1.2" "http://www.byond.com/download/build/${BYOND_MAJOR}/${BYOND_MAJOR}.${BYOND_MINOR}_byond_linux.zip" -o byond.zip unzip byond.zip rm byond.zip cd byond From 51cae2511c7a556050c65b5a2172bacd7c0aa942 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:04:10 -0700 Subject: [PATCH 02/44] Test 2: Ubuntu --- .github/workflows/ci_suite.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index d22c6be2de5..81d435a4056 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -10,7 +10,7 @@ jobs: run_linters: if: "!contains(github.event.head_commit.message, '[ci skip]')" name: Run Linters - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 concurrency: group: run_linters-${{ github.ref }} cancel-in-progress: true @@ -56,7 +56,7 @@ jobs: compile_all_maps: if: "!contains(github.event.head_commit.message, '[ci skip]')" name: Compile Maps - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 concurrency: group: compile_all_maps-${{ github.ref }} cancel-in-progress: true @@ -76,7 +76,7 @@ jobs: run_all_tests: if: "!contains(github.event.head_commit.message, '[ci skip]')" name: Integration Tests - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 services: mysql: image: mysql:latest @@ -127,7 +127,7 @@ jobs: if: "!contains(github.event.head_commit.message, '[ci skip]') && always()" needs: [run_all_tests] name: Compare Screenshot Tests - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v3 # If we ever add more artifacts, this is going to break, but it'll be obvious. From 121d56d2b3f43cc4db05edc56a88f4e0b5f4b07f Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:18:25 -0700 Subject: [PATCH 03/44] Test 3: Python Test 1 --- tools/requirements.txt | 4 ++-- tools/validate_dme.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/requirements.txt b/tools/requirements.txt index 60d73379ff9..44b33bf58bd 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,7 +1,7 @@ -pygit2==1.7.2 +pygit2==1.13.1 bidict==0.22.0 Pillow==9.0.1 # changelogs -PyYaml==5.4 +PyYaml==6.0.2 beautifulsoup4==4.9.3 diff --git a/tools/validate_dme.py b/tools/validate_dme.py index 4c49b7980fa..136278602be 100755 --- a/tools/validate_dme.py +++ b/tools/validate_dme.py @@ -84,7 +84,7 @@ def compare_lines(a, b): if a_segment != b_segment: return (a_segment > b_segment) - (a_segment < b_segment) - raise f"Two lines were exactly the same ({a} vs. {b})" + raise RuntimeError("Two lines were exactly the same ({a} vs. {b})") sorted_lines = sorted(lines, key = functools.cmp_to_key(compare_lines)) for (index, line) in enumerate(lines): From c78a028164735578b36081a902518c350310c0a1 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:30:59 -0700 Subject: [PATCH 04/44] Test 3 2: Python Test 2 --- .github/workflows/ci_suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index 81d435a4056..7e84b1c3f8a 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -32,7 +32,7 @@ jobs: ${{ runner.os }}- - name: Install Tools run: | - pip3 install setuptools + pip3 -r install tools/requirements.txt bash tools/ci/install_node.sh bash tools/ci/install_spaceman_dmm.sh dreamchecker tools/bootstrap/python -c '' From d4a774a8b81c464c16f10925b9a97dc85d214831 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:32:42 -0700 Subject: [PATCH 05/44] Test 3 2.1: Python Test 3 --- .github/workflows/ci_suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index 7e84b1c3f8a..d36d24121f1 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -32,7 +32,7 @@ jobs: ${{ runner.os }}- - name: Install Tools run: | - pip3 -r install tools/requirements.txt + pip3 install -r tools/requirements.txt bash tools/ci/install_node.sh bash tools/ci/install_spaceman_dmm.sh dreamchecker tools/bootstrap/python -c '' From 810b21b49b1c92958cbb286f28e9bff3dbcf4d70 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:44:03 -0700 Subject: [PATCH 06/44] Test 3 2.2: Python Test 4 --- .github/workflows/ci_suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index d36d24121f1..ca0d82f860e 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -32,7 +32,7 @@ jobs: ${{ runner.os }}- - name: Install Tools run: | - pip3 install -r tools/requirements.txt + pip -r install tools/requirements.txt bash tools/ci/install_node.sh bash tools/ci/install_spaceman_dmm.sh dreamchecker tools/bootstrap/python -c '' From 03a289fcab26b973fbe8417a21498d472e0d11b0 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:45:47 -0700 Subject: [PATCH 07/44] Python Test 5 --- .github/workflows/ci_suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index ca0d82f860e..29862da404c 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -32,7 +32,7 @@ jobs: ${{ runner.os }}- - name: Install Tools run: | - pip -r install tools/requirements.txt + pip install -r tools/requirements.txt bash tools/ci/install_node.sh bash tools/ci/install_spaceman_dmm.sh dreamchecker tools/bootstrap/python -c '' From 4caa4ee1b56a4299d9b12cb4b9ce757065b92c84 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 18:00:36 -0700 Subject: [PATCH 08/44] Test 3 2.4: Python Test 6 --- .github/workflows/ci_suite.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index 29862da404c..ee0dcf4464c 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -42,10 +42,10 @@ jobs: bash tools/ci/check_changelogs.sh bash tools/ci/check_grep.sh bash tools/ci/check_misc.sh - tools/bootstrap/python tools/validate_dme.py ${GITHUB_WORKSPACE}/output-annotations.txt 2>&1 - name: Annotate Lints uses: yogstation13/DreamAnnotate@v2 From 79d66ea80346898423a3e0ee7281aaf9bc875bcb Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 18:13:09 -0700 Subject: [PATCH 09/44] Python Test -1 --- .github/workflows/ci_suite.yml | 8 ++++---- tools/requirements.txt | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index ee0dcf4464c..81d435a4056 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -32,7 +32,7 @@ jobs: ${{ runner.os }}- - name: Install Tools run: | - pip install -r tools/requirements.txt + pip3 install setuptools bash tools/ci/install_node.sh bash tools/ci/install_spaceman_dmm.sh dreamchecker tools/bootstrap/python -c '' @@ -42,10 +42,10 @@ jobs: bash tools/ci/check_changelogs.sh bash tools/ci/check_grep.sh bash tools/ci/check_misc.sh - python tools/validate_dme.py ${GITHUB_WORKSPACE}/output-annotations.txt 2>&1 - name: Annotate Lints uses: yogstation13/DreamAnnotate@v2 diff --git a/tools/requirements.txt b/tools/requirements.txt index 44b33bf58bd..60d73379ff9 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,7 +1,7 @@ -pygit2==1.13.1 +pygit2==1.7.2 bidict==0.22.0 Pillow==9.0.1 # changelogs -PyYaml==6.0.2 +PyYaml==5.4 beautifulsoup4==4.9.3 From bbe2b296f04bf055571de0dd281efe68ea3d1d13 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 19:38:07 -0700 Subject: [PATCH 10/44] order test --- .github/workflows/ci_suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index 81d435a4056..769300c9c85 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -102,9 +102,9 @@ jobs: mysql -u root -proot tg_ci < schema.sql - name: Install rust-g run: | + sudo apt install -o APT::Immediate-Configure=false libssl1.1:i386 sudo dpkg --add-architecture i386 sudo apt update || true - sudo apt install -o APT::Immediate-Configure=false libssl1.1:i386 bash tools/ci/install_rust_g.sh - name: Compile Tests run: | From e426b232442f99aeb51cb098989db7974d6d4584 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 19:47:53 -0700 Subject: [PATCH 11/44] version change --- .github/workflows/ci_suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index 769300c9c85..0411e7ebb7a 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -102,9 +102,9 @@ jobs: mysql -u root -proot tg_ci < schema.sql - name: Install rust-g run: | - sudo apt install -o APT::Immediate-Configure=false libssl1.1:i386 sudo dpkg --add-architecture i386 sudo apt update || true + sudo apt install -o APT::Immediate-Configure=false libssl3:i386 bash tools/ci/install_rust_g.sh - name: Compile Tests run: | From 0f16ce8d0213e4be7dbae25694875c8110fb1257 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:18:13 -0700 Subject: [PATCH 12/44] format test --- code/modules/goonchat/browserOutput.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/goonchat/browserOutput.dm b/code/modules/goonchat/browserOutput.dm index a55ad22e170..3c60a675d01 100644 --- a/code/modules/goonchat/browserOutput.dm +++ b/code/modules/goonchat/browserOutput.dm @@ -227,7 +227,7 @@ GLOBAL_VAR_INIT(goonchatbasehtml, file2text('code/modules/goonchat/browserassets if(islist(target)) // Do the double-encoding outside the loop to save nanoseconds - var/twiceEncoded = url_encode(url_encode(message)) + var/twiceEncoded = url_encode("[url_encode(message)]") for(var/I in target) var/client/C = CLIENT_FROM_VAR(I) //Grab us a client if possible From 458de81bd185377f4a8e0bb796adcde6bd0addd4 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:24:34 -0700 Subject: [PATCH 13/44] feedback database fix --- tools/ci/ci_config.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/ci_config.txt b/tools/ci/ci_config.txt index 43cd480444f..c3416b66d0a 100644 --- a/tools/ci/ci_config.txt +++ b/tools/ci/ci_config.txt @@ -5,6 +5,6 @@ DATABASE tg_ci FEEDBACK_DATABASE tg_ci FEEDBACK_TABLEPREFIX FEEDBACK_LOGIN root -FEEDBACK_PASSWORD +FEEDBACK_PASSWORD root LAVALAND_BUDGET 0 SPACE_BUDGET 0 From 26e00f3fecc59d8ab66ffca79cffa3d70ddc94a3 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 21:58:52 -0700 Subject: [PATCH 14/44] undo encode test test rust --- .github/workflows/ci_suite.yml | 3 --- code/modules/goonchat/browserOutput.dm | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index 0411e7ebb7a..ee80bf99624 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -102,9 +102,6 @@ jobs: mysql -u root -proot tg_ci < schema.sql - name: Install rust-g run: | - sudo dpkg --add-architecture i386 - sudo apt update || true - sudo apt install -o APT::Immediate-Configure=false libssl3:i386 bash tools/ci/install_rust_g.sh - name: Compile Tests run: | diff --git a/code/modules/goonchat/browserOutput.dm b/code/modules/goonchat/browserOutput.dm index 3c60a675d01..a55ad22e170 100644 --- a/code/modules/goonchat/browserOutput.dm +++ b/code/modules/goonchat/browserOutput.dm @@ -227,7 +227,7 @@ GLOBAL_VAR_INIT(goonchatbasehtml, file2text('code/modules/goonchat/browserassets if(islist(target)) // Do the double-encoding outside the loop to save nanoseconds - var/twiceEncoded = url_encode("[url_encode(message)]") + var/twiceEncoded = url_encode(url_encode(message)) for(var/I in target) var/client/C = CLIENT_FROM_VAR(I) //Grab us a client if possible From c565fc20009c7eaa013a20e648c58d0c23f72460 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 22:02:04 -0700 Subject: [PATCH 15/44] revert test rust change --- .github/workflows/ci_suite.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index ee80bf99624..0411e7ebb7a 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -102,6 +102,9 @@ jobs: mysql -u root -proot tg_ci < schema.sql - name: Install rust-g run: | + sudo dpkg --add-architecture i386 + sudo apt update || true + sudo apt install -o APT::Immediate-Configure=false libssl3:i386 bash tools/ci/install_rust_g.sh - name: Compile Tests run: | From dba77fd568a19edef8d043c987112cd1df6e8bb9 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 22:22:51 -0700 Subject: [PATCH 16/44] version test again --- .github/workflows/ci_suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index 0411e7ebb7a..9d37525ecbe 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -104,7 +104,7 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt update || true - sudo apt install -o APT::Immediate-Configure=false libssl3:i386 + sudo apt install -o APT::Immediate-Configure=false libssl2:i386 bash tools/ci/install_rust_g.sh - name: Compile Tests run: | From 55df31c47674f26e274d314b06587c04d28dd6d7 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 22:24:55 -0700 Subject: [PATCH 17/44] version returns to 3 --- .github/workflows/ci_suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index 9d37525ecbe..0411e7ebb7a 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -104,7 +104,7 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt update || true - sudo apt install -o APT::Immediate-Configure=false libssl2:i386 + sudo apt install -o APT::Immediate-Configure=false libssl3:i386 bash tools/ci/install_rust_g.sh - name: Compile Tests run: | From 4636e71e13790ff2e8efaa4d2d0364f67265e9ea Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 23:51:35 -0700 Subject: [PATCH 18/44] python test 1/2 --- .github/workflows/ci_suite.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index 0411e7ebb7a..42171dc5f07 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -16,6 +16,16 @@ jobs: cancel-in-progress: true steps: - uses: actions/checkout@v3 + - name: Update apt and install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgit2-dev libjpeg62 libjpeg62-dev libpng-dev + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + cache: 'pip' + cache-dependency-path: 'tools/requirements.txt' + - uses: actions/checkout@v3 - name: Restore SpacemanDMM cache uses: actions/cache@v3 with: @@ -32,10 +42,9 @@ jobs: ${{ runner.os }}- - name: Install Tools run: | - pip3 install setuptools + pip install -r tools/requirements.txt bash tools/ci/install_node.sh bash tools/ci/install_spaceman_dmm.sh dreamchecker - tools/bootstrap/python -c '' - name: Run Linters run: | bash tools/ci/check_filedirs.sh cev_eris.dme From 3cfb6965bda9090ea070c31150b90ab802d6fec1 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 16 Oct 2025 23:57:28 -0700 Subject: [PATCH 19/44] python test 2/3... --- tools/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/requirements.txt b/tools/requirements.txt index 60d73379ff9..44b33bf58bd 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,7 +1,7 @@ -pygit2==1.7.2 +pygit2==1.13.1 bidict==0.22.0 Pillow==9.0.1 # changelogs -PyYaml==5.4 +PyYaml==6.0.2 beautifulsoup4==4.9.3 From ff33bb6e0338d5e594c215d839598027be771312 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 18 Oct 2025 03:09:26 -0700 Subject: [PATCH 20/44] potentially useful uncommenting --- tools/ci/run_server.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/run_server.sh b/tools/ci/run_server.sh index 44bb806ff46..6059419d58a 100755 --- a/tools/ci/run_server.sh +++ b/tools/ci/run_server.sh @@ -2,7 +2,7 @@ set -euo pipefail tools/deploy.sh ci_test -# mkdir ci_test/config +mkdir ci_test/config #test config cp tools/ci/ci_config.txt ci_test/config/config.txt From 37e561c07cf4edd5c42c6af4c0631a56e2c3ea89 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 18 Oct 2025 03:17:18 -0700 Subject: [PATCH 21/44] debug 2 --- tools/ci/run_server.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ci/run_server.sh b/tools/ci/run_server.sh index 6059419d58a..7e6f8a1e8b1 100755 --- a/tools/ci/run_server.sh +++ b/tools/ci/run_server.sh @@ -2,11 +2,11 @@ set -euo pipefail tools/deploy.sh ci_test -mkdir ci_test/config +# mkdir ci_test/config #test config cp tools/ci/ci_config.txt ci_test/config/config.txt - +cat ci_test/config/config.txt cd ci_test DreamDaemon cev_eris.dmb -close -trusted -verbose -params "log-directory=ci" cd .. From 7f1f5ddcd651fd1b38049a80cbfc84d92cd52c19 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 18 Oct 2025 03:32:57 -0700 Subject: [PATCH 22/44] debug better uncommenting was counterproductive --- code/controllers/configuration.dm | 1 + tools/ci/run_server.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index e951535ed60..c68593b850e 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -249,6 +249,7 @@ GLOBAL_LIST_EMPTY(storyteller_cache) /datum/configuration/proc/load(filename, type = "config") //the type can also be game_options, in which case it uses a different switch. not making it separate to not copypaste code - Urist var/list/Lines = file2list(filename) + log_misc("loading [filename]") for(var/t in Lines) if(!t) continue diff --git a/tools/ci/run_server.sh b/tools/ci/run_server.sh index 7e6f8a1e8b1..44bb806ff46 100755 --- a/tools/ci/run_server.sh +++ b/tools/ci/run_server.sh @@ -6,7 +6,7 @@ tools/deploy.sh ci_test #test config cp tools/ci/ci_config.txt ci_test/config/config.txt -cat ci_test/config/config.txt + cd ci_test DreamDaemon cev_eris.dmb -close -trusted -verbose -params "log-directory=ci" cd .. From 6366f42f7aabeac94f7de2a5aa27a16cac7efd2e Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 18 Oct 2025 03:38:04 -0700 Subject: [PATCH 23/44] this might log properly --- code/controllers/configuration.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index c68593b850e..9eda63aa343 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -249,7 +249,7 @@ GLOBAL_LIST_EMPTY(storyteller_cache) /datum/configuration/proc/load(filename, type = "config") //the type can also be game_options, in which case it uses a different switch. not making it separate to not copypaste code - Urist var/list/Lines = file2list(filename) - log_misc("loading [filename]") + world.log << "loading [filename]" for(var/t in Lines) if(!t) continue From f932ffcec532c875d35ea85a5393716d11636512 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 18 Oct 2025 03:41:44 -0700 Subject: [PATCH 24/44] go go go further testing --- code/game/world.dm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/game/world.dm b/code/game/world.dm index 0f44e13a4a8..43fd817a9b5 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -15,6 +15,7 @@ var/global/datum/global_init/init = new () */ /datum/global_init/New() + world.log << "attempted init" generate_gameid() load_configuration() makeDatumRefLists() @@ -252,6 +253,7 @@ var/world_topic_spam_protect_time = world.timeofday /proc/load_configuration() + world.log << "attempted load" config = new /datum/configuration() config.load("config/config.txt") config.load("config/game_options.txt", "game_options") From 9890dc89c09b19b0a0a820ee6b40355b5b74731c Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 23 Oct 2025 07:03:23 -0700 Subject: [PATCH 25/44] trim ci_config.txt replace config.txt autogeneration to dbconfig.txt for compatibility --- tools/ci/ci_config.txt | 2 -- tools/ci/run_server.sh | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/ci/ci_config.txt b/tools/ci/ci_config.txt index c3416b66d0a..abc745e54da 100644 --- a/tools/ci/ci_config.txt +++ b/tools/ci/ci_config.txt @@ -6,5 +6,3 @@ FEEDBACK_DATABASE tg_ci FEEDBACK_TABLEPREFIX FEEDBACK_LOGIN root FEEDBACK_PASSWORD root -LAVALAND_BUDGET 0 -SPACE_BUDGET 0 diff --git a/tools/ci/run_server.sh b/tools/ci/run_server.sh index 44bb806ff46..259f5a6c188 100755 --- a/tools/ci/run_server.sh +++ b/tools/ci/run_server.sh @@ -5,7 +5,7 @@ tools/deploy.sh ci_test # mkdir ci_test/config #test config -cp tools/ci/ci_config.txt ci_test/config/config.txt +cp tools/ci/ci_config.txt ci_test/config/dbconfig.txt cd ci_test DreamDaemon cev_eris.dmb -close -trusted -verbose -params "log-directory=ci" From e8bd2529f8f71d6c3908b09a116bbcbf6c289130 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 23 Oct 2025 07:09:38 -0700 Subject: [PATCH 26/44] even more debug --- code/controllers/configuration.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 9eda63aa343..40de116aef0 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -780,6 +780,7 @@ GLOBAL_LIST_EMPTY(storyteller_cache) /datum/configuration/proc/loadsql(filename) // -- TLE var/list/Lines = file2list(filename) + world.log << "loading [filename]" for(var/t in Lines) if(!t) continue From ce4846ddf37c65a5b76899ce8717ee3ba91bf6db Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 23 Oct 2025 07:13:52 -0700 Subject: [PATCH 27/44] config file returned to the cold ways --- tools/ci/ci_config.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/ci/ci_config.txt b/tools/ci/ci_config.txt index abc745e54da..3bac86f7dbf 100644 --- a/tools/ci/ci_config.txt +++ b/tools/ci/ci_config.txt @@ -1,8 +1,8 @@ SQL_ENABLED -ADDRESS 127.0.0.1 -PORT 3306 -DATABASE tg_ci +address 127.0.0.1 +port 3306 +database tg_ci FEEDBACK_DATABASE tg_ci FEEDBACK_TABLEPREFIX -FEEDBACK_LOGIN root -FEEDBACK_PASSWORD root +login root +password root From 56e6da22ffbe1896c8d4bb6208dbc411a7224f65 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 23 Oct 2025 07:22:46 -0700 Subject: [PATCH 28/44] colder still pruned and transparent --- code/controllers/configuration.dm | 5 +++++ tools/ci/ci_config.txt | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 40de116aef0..a8d8e899fce 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -806,14 +806,19 @@ GLOBAL_LIST_EMPTY(storyteller_cache) switch (name) if ("address") sqladdress = value + world.log << "adr [value]" if ("port") sqlport = value + world.log << "port [value]" if ("database") sqldb = value + world.log << "db [value]" if ("login") sqllogin = value + world.log << "db [value]" if ("password") sqlpass = value + world.log << "adr [value]" else log_misc("Unknown setting in configuration: '[name]'") diff --git a/tools/ci/ci_config.txt b/tools/ci/ci_config.txt index 3bac86f7dbf..fc143c33b3c 100644 --- a/tools/ci/ci_config.txt +++ b/tools/ci/ci_config.txt @@ -1,8 +1,5 @@ -SQL_ENABLED address 127.0.0.1 port 3306 database tg_ci -FEEDBACK_DATABASE tg_ci -FEEDBACK_TABLEPREFIX login root password root From 9d565fce0a5b7d49dbd512f89c84c889f7a954b1 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:08:53 -0700 Subject: [PATCH 29/44] mysql test log_world to standard --- code/game/world.dm | 4 ++-- schema.sql | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/code/game/world.dm b/code/game/world.dm index 43fd817a9b5..6c02b47b946 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -15,7 +15,7 @@ var/global/datum/global_init/init = new () */ /datum/global_init/New() - world.log << "attempted init" + log_world("attempted init") generate_gameid() load_configuration() makeDatumRefLists() @@ -253,7 +253,7 @@ var/world_topic_spam_protect_time = world.timeofday /proc/load_configuration() - world.log << "attempted load" + log_world("attempted load of configuration") config = new /datum/configuration() config.load("config/config.txt") config.load("config/game_options.txt", "game_options") diff --git a/schema.sql b/schema.sql index 142a6bd61fd..a7d5796b687 100644 --- a/schema.sql +++ b/schema.sql @@ -28,7 +28,7 @@ CREATE TABLE `ar_internal_metadata` ( `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, PRIMARY KEY (`key`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -62,7 +62,7 @@ CREATE TABLE `bans` ( CONSTRAINT `fk_rails_20d480679b` FOREIGN KEY (`banned_by_id`) REFERENCES `players` (`id`), CONSTRAINT `fk_rails_62ac37e1e1` FOREIGN KEY (`target_id`) REFERENCES `players` (`id`), CONSTRAINT `fk_rails_a305c9e562` FOREIGN KEY (`unbanned_by_id`) REFERENCES `players` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -84,7 +84,7 @@ CREATE TABLE `books` ( PRIMARY KEY (`id`), KEY `index_books_on_author_id` (`author_id`), CONSTRAINT `fk_rails_53d51ce16a` FOREIGN KEY (`author_id`) REFERENCES `players` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -110,7 +110,7 @@ CREATE TABLE `players` ( `ip_related_ids` tinytext, `cid_related_ids` tinytext, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -129,7 +129,7 @@ CREATE TABLE `poll_options` ( PRIMARY KEY (`id`), KEY `index_poll_options_on_poll_id` (`poll_id`), CONSTRAINT `fk_rails_aa85becb42` FOREIGN KEY (`poll_id`) REFERENCES `polls` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -150,7 +150,7 @@ CREATE TABLE `poll_text_replies` ( KEY `index_poll_text_replies_on_player_id` (`player_id`), CONSTRAINT `fk_rails_0833f4df0b` FOREIGN KEY (`poll_id`) REFERENCES `polls` (`id`), CONSTRAINT `fk_rails_ffc8df499f` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -173,7 +173,7 @@ CREATE TABLE `poll_votes` ( CONSTRAINT `fk_rails_826ebfbbb3` FOREIGN KEY (`option_id`) REFERENCES `poll_options` (`id`), CONSTRAINT `fk_rails_a3e5a3aede` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`), CONSTRAINT `fk_rails_a6e6974b7e` FOREIGN KEY (`poll_id`) REFERENCES `polls` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -190,7 +190,7 @@ CREATE TABLE `polls` ( `end` datetime NOT NULL, `question` varchar(255) NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -207,7 +207,7 @@ CREATE TABLE `populations` ( `time` datetime NOT NULL, `server` varchar(255) NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -220,7 +220,7 @@ DROP TABLE IF EXISTS `schema_migrations`; CREATE TABLE `schema_migrations` ( `version` varchar(255) NOT NULL, PRIMARY KEY (`version`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; From ae2241c3aa9b5d69eeb2eb4d9b3be8c57c6dff2a Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:13:47 -0700 Subject: [PATCH 30/44] test again --- schema.sql | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/schema.sql b/schema.sql index a7d5796b687..3f0808a631b 100644 --- a/schema.sql +++ b/schema.sql @@ -14,7 +14,7 @@ /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; - +/*edited*/ -- -- Table structure for table `ar_internal_metadata` -- @@ -28,7 +28,7 @@ CREATE TABLE `ar_internal_metadata` ( `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, PRIMARY KEY (`key`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -62,7 +62,7 @@ CREATE TABLE `bans` ( CONSTRAINT `fk_rails_20d480679b` FOREIGN KEY (`banned_by_id`) REFERENCES `players` (`id`), CONSTRAINT `fk_rails_62ac37e1e1` FOREIGN KEY (`target_id`) REFERENCES `players` (`id`), CONSTRAINT `fk_rails_a305c9e562` FOREIGN KEY (`unbanned_by_id`) REFERENCES `players` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -84,7 +84,7 @@ CREATE TABLE `books` ( PRIMARY KEY (`id`), KEY `index_books_on_author_id` (`author_id`), CONSTRAINT `fk_rails_53d51ce16a` FOREIGN KEY (`author_id`) REFERENCES `players` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -110,7 +110,7 @@ CREATE TABLE `players` ( `ip_related_ids` tinytext, `cid_related_ids` tinytext, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -129,7 +129,7 @@ CREATE TABLE `poll_options` ( PRIMARY KEY (`id`), KEY `index_poll_options_on_poll_id` (`poll_id`), CONSTRAINT `fk_rails_aa85becb42` FOREIGN KEY (`poll_id`) REFERENCES `polls` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -150,7 +150,7 @@ CREATE TABLE `poll_text_replies` ( KEY `index_poll_text_replies_on_player_id` (`player_id`), CONSTRAINT `fk_rails_0833f4df0b` FOREIGN KEY (`poll_id`) REFERENCES `polls` (`id`), CONSTRAINT `fk_rails_ffc8df499f` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -173,7 +173,7 @@ CREATE TABLE `poll_votes` ( CONSTRAINT `fk_rails_826ebfbbb3` FOREIGN KEY (`option_id`) REFERENCES `poll_options` (`id`), CONSTRAINT `fk_rails_a3e5a3aede` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`), CONSTRAINT `fk_rails_a6e6974b7e` FOREIGN KEY (`poll_id`) REFERENCES `polls` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -190,7 +190,7 @@ CREATE TABLE `polls` ( `end` datetime NOT NULL, `question` varchar(255) NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -207,7 +207,7 @@ CREATE TABLE `populations` ( `time` datetime NOT NULL, `server` varchar(255) NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -220,7 +220,7 @@ DROP TABLE IF EXISTS `schema_migrations`; CREATE TABLE `schema_migrations` ( `version` varchar(255) NOT NULL, PRIMARY KEY (`version`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; From 314711d88a85552006f9a3e400c4080db86c95ef Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:46:10 -0700 Subject: [PATCH 31/44] integration tests test --- .github/workflows/ci_suite.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index 42171dc5f07..73bac2c4597 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -98,6 +98,10 @@ jobs: group: run_all_tests-${{ github.ref }} cancel-in-progress: true steps: + - name: Update apt and install libc6 + run: | + sudo apt-get update + sudo apt-get install -y libc6 - uses: actions/checkout@v3 - name: Restore BYOND cache uses: actions/cache@v3 From 1230711561eefccfe4a633fecd63ca5b80bc0724 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:54:32 -0700 Subject: [PATCH 32/44] remove some useless stuff --- .github/workflows/ci_suite.yml | 4 ---- code/controllers/configuration.dm | 5 ----- 2 files changed, 9 deletions(-) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index 73bac2c4597..42171dc5f07 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -98,10 +98,6 @@ jobs: group: run_all_tests-${{ github.ref }} cancel-in-progress: true steps: - - name: Update apt and install libc6 - run: | - sudo apt-get update - sudo apt-get install -y libc6 - uses: actions/checkout@v3 - name: Restore BYOND cache uses: actions/cache@v3 diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index a8d8e899fce..40de116aef0 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -806,19 +806,14 @@ GLOBAL_LIST_EMPTY(storyteller_cache) switch (name) if ("address") sqladdress = value - world.log << "adr [value]" if ("port") sqlport = value - world.log << "port [value]" if ("database") sqldb = value - world.log << "db [value]" if ("login") sqllogin = value - world.log << "db [value]" if ("password") sqlpass = value - world.log << "adr [value]" else log_misc("Unknown setting in configuration: '[name]'") From 509427a93ef3101bffa1779dc63f5da29a357a48 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 25 Oct 2025 09:28:36 -0700 Subject: [PATCH 33/44] sql test --- cev_eris.dme | 4 +- code/__DEFINES/subsystems-priority.dm | 3 +- code/__DEFINES/subsystems.dm | 10 + code/__HELPERS/text.dm | 6 +- code/controllers/configuration.dm | 14 +- code/datums/topic/admin.dm | 18 ++ code/defines/procs/dbcore.dm | 235 ------------------ code/game/world.dm | 45 ---- code/global.dm | 10 +- code/modules/admin/DB ban/delayed_ban.dm | 15 +- code/modules/admin/DB ban/functions.dm | 93 ++++--- code/modules/admin/DB_search/search.dm | 21 +- code/modules/admin/IsBanned.dm | 18 +- code/modules/admin/admin_ranks.dm | 12 +- code/modules/admin/admin_verbs.dm | 1 + code/modules/admin/banjob.dm | 10 +- .../admin/permissionverbs/permissionedit.dm | 28 ++- code/modules/admin/verbs/antiraid.dm | 18 +- code/modules/client/client_procs.dm | 78 ++++-- code/modules/library/lib_machines.dm | 132 +++++----- code/modules/mob/new_player/new_player.dm | 15 +- code/modules/mob/new_player/poll.dm | 71 ++++-- .../file_system/programs/generic/library.dm | 33 ++- config/example/dbconfig.txt | 26 ++ 24 files changed, 404 insertions(+), 512 deletions(-) delete mode 100644 code/defines/procs/dbcore.dm diff --git a/cev_eris.dme b/cev_eris.dme index 95a6ad58348..beef362881d 100644 --- a/cev_eris.dme +++ b/cev_eris.dme @@ -30,6 +30,7 @@ #include "code\__DEFINES\craft.dm" #include "code\__DEFINES\cyborg_traits.dm" #include "code\__DEFINES\damage_organs.dm" +#include "code\__DEFINES\database.dm" #include "code\__DEFINES\economy.dm" #include "code\__DEFINES\error_handler.dm" #include "code\__DEFINES\flags.dm" @@ -226,6 +227,7 @@ #include "code\controllers\subsystems\chemistry.dm" #include "code\controllers\subsystems\chunks.dm" #include "code\controllers\subsystems\craft.dm" +#include "code\controllers\subsystems\database.dm" #include "code\controllers\subsystems\dcs.dm" #include "code\controllers\subsystems\economy.dm" #include "code\controllers\subsystems\evac.dm" @@ -527,7 +529,6 @@ #include "code\defines\obj.dm" #include "code\defines\procs\announce.dm" #include "code\defines\procs\AStar.dm" -#include "code\defines\procs\dbcore.dm" #include "code\defines\procs\hud.dm" #include "code\defines\procs\radio.dm" #include "code\defines\procs\sd_Alert.dm" @@ -1425,6 +1426,7 @@ #include "code\modules\admin\verbs\possess.dm" #include "code\modules\admin\verbs\pray.dm" #include "code\modules\admin\verbs\randomverbs.dm" +#include "code\modules\admin\verbs\reestablish_db_connection.dm" #include "code\modules\admin\verbs\ticklag.dm" #include "code\modules\admin\verbs\tripAI.dm" #include "code\modules\admin\verbs\SDQL_2\SDQL_2.dm" diff --git a/code/__DEFINES/subsystems-priority.dm b/code/__DEFINES/subsystems-priority.dm index 2b93e424563..cad0de31b11 100644 --- a/code/__DEFINES/subsystems-priority.dm +++ b/code/__DEFINES/subsystems-priority.dm @@ -16,7 +16,8 @@ var/list/bitflags = list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096 #define SS_PRIORITY_TICKER 200 // Gameticker processing. #define FIRE_PRIORITY_TGUI 110 #define FIRE_PRIORITY_EXPLOSIONS 105 // Explosions! -#define FIRE_PRIORITY_THROWING 106 // Throwing! after explosions since they influence throw direction +#define FIRE_PRIORITY_DATABASE 104 +#define FIRE_PRIORITY_THROWING 103 // Throwing! after explosions since they influence throw direction #define SS_PRIORITY_HUMAN 101 // Human Life(). #define SS_PRIORITY_MOB 100 // Non-human Mob Life(). #define SS_PRIORITY_CHAT 100 // Chat subsystem. diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 0600cf513d4..0c507b6a63d 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -1,4 +1,13 @@ +//! ## DB defines +/** + * DB schema version + * + * Update this whenever the db schema changes + */ +#define DB_SCHEMA_VERSION 1 + + //! ## Timing subsystem /** * Don't run if there is an identical unique timer active @@ -88,6 +97,7 @@ // The numbers just define the ordering, they are meaningless otherwise. #define INIT_ORDER_GARBAGE 99 +#define INIT_ORDER_DBCORE 98 #define INIT_ORDER_EXPLOSIONS 97 #define INIT_ORDER_STATPANELS 96 #define INIT_ORDER_MAPPING 15 diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index 0a035e76bfa..65e47ae9d29 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -13,10 +13,8 @@ * SQL sanitization */ -// Run all strings to be used in an SQL query through this proc first to properly escape out injection attempts. -/proc/sanitizeSQL(var/t as text) - var/sqltext = dbcon.Quote(t); - return copytext(sqltext, 2, length(sqltext));//Quote() adds quotes around input, we already do that +/proc/format_table_name(table as text) + return sql_tableprefix + table /* * Text sanitization diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 40de116aef0..b9f9c0e7e21 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -807,13 +807,25 @@ GLOBAL_LIST_EMPTY(storyteller_cache) if ("address") sqladdress = value if ("port") - sqlport = value + sqlport = text2num(value) if ("database") sqldb = value if ("login") sqllogin = value if ("password") sqlpass = value + if ("sql_tableprefix") + sql_tableprefix = value + if ("async_query_timeout") + sql_async_query_timeout = text2num(value) + if ("blocking_query_timeout") + sql_blocking_query_timeout = text2num(value) + if ("pooling_min_sql_connections") + sql_pooling_min_sql_connections = text2num(value) + if ("pooling_max_sql_connections") + sql_pooling_max_sql_connections = text2num(value) + if ("max_concurrent_queries") + sql_max_concurrent_queries = text2num(value) else log_misc("Unknown setting in configuration: '[name]'") diff --git a/code/datums/topic/admin.dm b/code/datums/topic/admin.dm index f3af9e40f72..1ab4748577d 100644 --- a/code/datums/topic/admin.dm +++ b/code/datums/topic/admin.dm @@ -1353,6 +1353,24 @@ else error_viewer.showTo(usr, null, input["viewruntime_linear"]) +/datum/admin_topic/slowquery + keyword = "slowquery" + require_perms = list(R_ADMIN) + +/datum/admin_topic/viewruntime/Run(list/input) + if(!check_rights(R_ADMIN)) + return + + var/data = list("key" = usr.key) + var/answer = input["slowquery"] + if(answer == "yes") + if(alert(usr, "Did you just press any admin buttons?", "Query server hang report", list("Yes", "No")) == "Yes") + var/response = input(usr,"What were you just doing?","Query server hang report") as null|text + if(response) + data["response"] = response + log_misc("SQL: server hang - [json_encode(data)]") + else if(answer == "no") + log_misc("SQL: no server hang - [json_encode(data)]") /datum/admin_topic/admincaster keyword = "admincaster" diff --git a/code/defines/procs/dbcore.dm b/code/defines/procs/dbcore.dm deleted file mode 100644 index fb0f19b6e10..00000000000 --- a/code/defines/procs/dbcore.dm +++ /dev/null @@ -1,235 +0,0 @@ -//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31 - -//cursors -#define Default_Cursor 0 -#define Client_Cursor 1 -#define Server_Cursor 2 -//conversions -#define TEXT_CONV 1 -#define RSC_FILE_CONV 2 -#define NUMBER_CONV 3 -//column flag values: -#define IS_NUMERIC 1 -#define IS_BINARY 2 -#define IS_NOT_NULL 4 -#define IS_PRIMARY_KEY 8 -#define IS_UNSIGNED 16 -//types -#define TINYINT 1 -#define SMALLINT 2 -#define MEDIUMINT 3 -#define INTEGER 4 -#define BIGINT 5 -#define DECIMAL 6 -#define FLOAT 7 -#define DOUBLE 8 -#define DATE 9 -#define DATETIME 10 -#define TIMESTAMP 11 -#define TIME 12 -#define STRING 13 -#define BLOB 14 -// TODO: Investigate more recent type additions and see if I can handle them. - Nadrew - - -// Deprecated! See global.dm for new configuration vars -/* -var/DB_SERVER = "" // This is the location of your MySQL server (localhost is USUALLY fine) -var/DB_PORT = 3306 // This is the port your MySQL server is running on (3306 is the default) -*/ - -DBConnection - var/_db_con // This variable contains a reference to the actual database connection. - var/dbi // This variable is a string containing the DBI MySQL requires. - var/user // This variable contains the username data. - var/password // This variable contains the password data. - var/default_cursor // This contains the default database cursor data. - var/server = "" - var/port = 3306 - -DBConnection/New(dbi_handler, username, password_handler, cursor_handler) - src.dbi = dbi_handler - src.user = username - src.password = password_handler - src.default_cursor = cursor_handler - _db_con = _dm_db_new_con() - -DBConnection/proc/Connect(dbi_handler=src.dbi, user_handler=src.user, password_handler=src.password, cursor_handler) - if(!src) - return 0 - cursor_handler = src.default_cursor - if(!cursor_handler) - cursor_handler = Default_Cursor - return _dm_db_connect(_db_con, dbi_handler, user_handler, password_handler, cursor_handler, null) - -DBConnection/proc/Disconnect() - return _dm_db_close(_db_con) - -DBConnection/proc/IsConnected() - return _dm_db_is_connected(_db_con) - -DBConnection/proc/Quote(str) - return _dm_db_quote(_db_con, str) - -DBConnection/proc/ErrorMsg() - return "## MYSQL ERROR: [_dm_db_error_msg(_db_con)]" - -DBConnection/proc/SelectDB(database_name, dbi) - if(IsConnected()) - Disconnect() - return Connect("[dbi?"[dbi]":"dbi:mysql:[database_name]:[sqladdress]:[sqlport]"]", user, password) - -DBConnection/proc/NewQuery(sql_query, cursor_handler = src.default_cursor) - return new/DBQuery(sql_query, src, cursor_handler) - - -DBQuery/New(sql_query, DBConnection/connection_handler, cursor_handler) - if(sql_query) - src.sql = sql_query - if(connection_handler) - src.db_connection = connection_handler - if(cursor_handler) - src.default_cursor = cursor_handler - _db_query = _dm_db_new_query() - return ..() - - -DBQuery - var/sql // The sql query being executed. - var/default_cursor - var/list/columns //list of DB Columns populated by Columns() - var/list/conversions - var/list/item[0] //list of data values populated by NextRow() - - var/DBConnection/db_connection - var/_db_query - -DBQuery/proc/Connect(DBConnection/connection_handler) - src.db_connection = connection_handler - -DBQuery/proc/Execute(sql_query = src.sql, cursor_handler = default_cursor) - Close() - return _dm_db_execute(_db_query, sql_query, db_connection._db_con, cursor_handler, null) - -DBQuery/proc/NextRow() - return _dm_db_next_row(_db_query, item, conversions) - -DBQuery/proc/RowsAffected() - return _dm_db_rows_affected(_db_query) - -DBQuery/proc/RowCount() - return _dm_db_row_count(_db_query) - -DBQuery/proc/ErrorMsg() - return _dm_db_error_msg(_db_query) - -DBQuery/proc/Columns() - if(!columns) - columns = _dm_db_columns(_db_query, /DBColumn) - return columns - -DBQuery/proc/GetRowData() - var/list/columns = Columns() - var/list/results - if(columns.len) - results = list() - for(var/C in columns) - results.Add(C) - var/DBColumn/cur_col = columns[C] - results[C] = src.item[(cur_col.position + 1)] - return results - -DBQuery/proc/Close() - item.len = 0 - columns = null - conversions = null - return _dm_db_close(_db_query) - -DBQuery/proc/Quote(str) - return db_connection.Quote(str) - -DBQuery/proc/SetConversion(column, conversion) - if(istext(column)) - column = columns.Find(column) - if(!conversions) - conversions = new/list(column) - else if(conversions.len < column) - conversions.len = column - conversions[column] = conversion - - -DBColumn - var/name - var/table - var/position //1-based index into item data - var/sql_type - var/flags - var/length - var/max_length - -DBColumn/New(name_handler, table_handler, position_handler, type_handler, flag_handler, length_handler, max_length_handler) - src.name = name_handler - src.table = table_handler - src.position = position_handler - src.sql_type = type_handler - src.flags = flag_handler - src.length = length_handler - src.max_length = max_length_handler - return ..() - - -DBColumn/proc/SqlTypeName(type_handler = src.sql_type) - switch(type_handler) - if(TINYINT) - return "TINYINT" - if(SMALLINT) - return "SMALLINT" - if(MEDIUMINT) - return "MEDIUMINT" - if(INTEGER) - return "INTEGER" - if(BIGINT) - return "BIGINT" - if(FLOAT) - return "FLOAT" - if(DOUBLE) - return "DOUBLE" - if(DATE) - return "DATE" - if(DATETIME) - return "DATETIME" - if(TIMESTAMP) - return "TIMESTAMP" - if(TIME) - return "TIME" - if(STRING) - return "STRING" - if(BLOB) - return "BLOB" - - -#undef Default_Cursor -#undef Client_Cursor -#undef Server_Cursor -#undef TEXT_CONV -#undef RSC_FILE_CONV -#undef NUMBER_CONV -#undef IS_NUMERIC -#undef IS_BINARY -#undef IS_NOT_NULL -#undef IS_PRIMARY_KEY -#undef IS_UNSIGNED -#undef TINYINT -#undef SMALLINT -#undef MEDIUMINT -#undef INTEGER -#undef BIGINT -#undef DECIMAL -#undef FLOAT -#undef DOUBLE -#undef DATE -#undef DATETIME -#undef TIMESTAMP -#undef TIME -#undef STRING -#undef BLOB diff --git a/code/game/world.dm b/code/game/world.dm index 6c02b47b946..90ca10379b1 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -359,51 +359,6 @@ var/world_topic_spam_protect_time = world.timeofday if (src.status != s) src.status = s -#define FAILED_DB_CONNECTION_CUTOFF 5 -var/failed_db_connections = 0 -var/failed_old_db_connections = 0 - -/hook/startup/proc/connectDB() - if(!setup_database_connection()) - log_world("Your server failed to establish a connection with the feedback database.") - else - log_world("Feedback database connection established.") - return 1 - -proc/setup_database_connection() - - if(failed_db_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to conenct anymore. - return 0 - - if(!dbcon) - dbcon = new() - - var/user = sqllogin - var/pass = sqlpass - var/db = sqldb - var/address = sqladdress - var/port = sqlport - - dbcon.Connect("dbi:mysql:[db]:[address]:[port]", "[user]", "[pass]") - . = dbcon.IsConnected() - if ( . ) - failed_db_connections = 0 //If this connection succeeded, reset the failed connections counter. - else - failed_db_connections++ //If it failed, increase the failed connections counter. - log_world(dbcon.ErrorMsg()) - - return . - -//This proc ensures that the connection to the feedback database (global variable dbcon) is established -proc/establish_db_connection() - if(failed_db_connections > FAILED_DB_CONNECTION_CUTOFF) - return 0 - - if(!dbcon || !dbcon.IsConnected()) - return setup_database_connection() - else - return 1 - /world/proc/incrementMaxZ(z_level_info) SEND_SIGNAL(SSdcs, COMSIG_WORLD_MAXZ_INCREMENTING) maxz++ diff --git a/code/global.dm b/code/global.dm index 18a0e95ca76..8efa74fbe3c 100644 --- a/code/global.dm +++ b/code/global.dm @@ -66,16 +66,18 @@ var/sqlport var/sqldb var/sqllogin var/sqlpass +var/sql_tableprefix +var/sql_async_query_timeout = 10 +var/sql_blocking_query_timeout = 5 +var/sql_pooling_min_sql_connections = 1 +var/sql_pooling_max_sql_connections = 25 +var/sql_max_concurrent_queries = 25 // For FTP requests. (i.e. downloading runtime logs.) // However it'd be ok to use for accessing attack logs and such too, which are even laggier. var/fileaccess_timer = 0 var/custom_event_msg -// Database connections. A connection is established on world creation. -// Ideally, the connection dies when the server restarts (After feedback logging.). -var/DBConnection/dbcon = new() // Feedback database (New database) - // Reference list for disposal sort junctions. Filled up by sorting junction's New() /var/list/tagger_locations = list() diff --git a/code/modules/admin/DB ban/delayed_ban.dm b/code/modules/admin/DB ban/delayed_ban.dm index 3a403110856..b62040d5cfc 100644 --- a/code/modules/admin/DB ban/delayed_ban.dm +++ b/code/modules/admin/DB ban/delayed_ban.dm @@ -24,7 +24,20 @@ GLOBAL_LIST_EMPTY(delayed_bans) banned_by_id = _banned_by_id /datum/delayed_ban/proc/execute() - var/DBQuery/query_insert = dbcon.NewQuery({"INSERT INTO bans (target_id, time, server, type, reason, job, duration, expiration_time, cid, ip, banned_by_id) VALUES ([target_id], Now(), '[server]', '[bantype_str]', '[reason]', '[job]', [(duration)?"[duration]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[computerid]', NULL, [banned_by_id])"}) + var/datum/db_query/query_insert = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("bans")] \ + (target_id, time, server, type, reason, job, duration, expiration_time, cid, ip, banned_by_id) \ + VALUES (:target_id, Now(), :server, :type, :reason, :job, [duration ? "[duration]" : "0"], Now() + INTERVAL [duration > 0 ? duration : 0] MINUTE, :cid, NULL, :banned_by_id)", + list( + "target_id" = target_id, + "server" = server, + "type" = bantype_str, + "reason" = reason, + "job" = job, + "cid" = computerid, + "banned_by_id" = banned_by_id, + ) + ) query_insert.Execute() /hook/roundend/proc/explode() diff --git a/code/modules/admin/DB ban/functions.dm b/code/modules/admin/DB ban/functions.dm index 2cfe8bb1ec2..06895df1de9 100644 --- a/code/modules/admin/DB ban/functions.dm +++ b/code/modules/admin/DB ban/functions.dm @@ -5,8 +5,7 @@ datum/admins/proc/DB_ban_record(var/bantype, var/mob/banned_mob, var/duration = return - establish_db_connection() - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) if(banned_mob.ckey) error("[key_name_admin(usr)] attempted to ban [banned_mob.ckey], but somehow server could not establish a database connection.") else @@ -45,7 +44,7 @@ datum/admins/proc/DB_ban_record(var/bantype, var/mob/banned_mob, var/duration = var/target_id var/banned_by_id - var/DBQuery/query + var/datum/db_query/query if(ismob(banned_mob)) ckey = banned_mob.ckey @@ -59,7 +58,7 @@ datum/admins/proc/DB_ban_record(var/bantype, var/mob/banned_mob, var/duration = ip = banip if(!target_id) - query = dbcon.NewQuery("SELECT id FROM players WHERE ckey = '[ckey]'") + query = SSdbcore.NewQuery("SELECT id FROM [format_table_name("players")] WHERE ckey = :ckey", list("ckey" = ckey)) query.Execute() if(!query.NextRow()) if(!banned_mob || (banned_mob && !IsGuestKey(banned_mob.key))) @@ -70,7 +69,7 @@ datum/admins/proc/DB_ban_record(var/bantype, var/mob/banned_mob, var/duration = banned_by_id = usr.client.id if(!banned_by_id) - query = dbcon.NewQuery("SELECT id FROM players WHERE ckey = '[usr.ckey]'") + query = SSdbcore.NewQuery("SELECT id FROM [format_table_name("players")] WHERE ckey = :ckey", list("ckey" = usr.ckey)) query.Execute() if(!query.NextRow()) error("[key_name_admin(usr)] attempted to ban [ckey], but somehow [key_name_admin(usr)] record does not exist in database.") @@ -80,21 +79,32 @@ datum/admins/proc/DB_ban_record(var/bantype, var/mob/banned_mob, var/duration = reason = sql_sanitize_text(reason) if(!computerid) - var/DBQuery/get_cid = dbcon.NewQuery("SELECT cid FROM players WHERE id = '[target_id]'") + var/datum/db_query/get_cid = SSdbcore.NewQuery("SELECT cid FROM [format_table_name("players")] WHERE id = :id", list("id" = target_id)) get_cid.Execute() if(get_cid.NextRow()) computerid = get_cid.item[1] - var/sql if(delayed_ban) - var/datum/delayed_ban/ban = new(target_id, server, bantype_str , reason, job, duration, computerid, banned_by_id, ip) + var/datum/delayed_ban/ban = new(target_id, server, bantype_str, reason, job, duration, computerid, banned_by_id, ip) GLOB.delayed_bans += ban return - if(banip == -1) - sql = "INSERT INTO bans (target_id, time, server, type, reason, job, duration, expiration_time, cid, ip, banned_by_id) VALUES ([target_id], Now(), '[server]', '[bantype_str]', '[reason]', '[job]', [(duration)?"[duration]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[computerid]', NULL, [banned_by_id])" - else - sql = "INSERT INTO bans (target_id, time, server, type, reason, job, duration, expiration_time, cid, ip, banned_by_id) VALUES ([target_id], Now(), '[server]', '[bantype_str]', '[reason]', '[job]', [(duration)?"[duration]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[computerid]', '[ip]', [banned_by_id])" - var/DBQuery/query_insert = dbcon.NewQuery(sql) + var/datum/db_query/query_insert = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("bans")] \ + (target_id, time, server, type, reason, job, duration, expiration_time, cid, ip, banned_by_id) \ + VALUES (:target_id, Now(), :server, :type, :reason, :job, :duration, Now() + INTERVAL :duration_minutes MINUTE, :cid, '[banip == -1 ? "NULL" : ip]', :banned_by_id)", + list( + "target_id" = target_id, + "server" = server, + "type" = bantype_str, + "reason" = reason, + "job" = job, + "duration" = duration ? duration : 0, + "duration_minutes" = duration > 0 ? duration : 0, + "cid" = computerid, + "banned_by_id" = banned_by_id + ) + ) + if(!query_insert.Execute()) log_world("[key_name_admin(usr)] attempted to ban [ckey] but got error: [query_insert.ErrorMsg()].") return @@ -107,8 +117,7 @@ datum/admins/proc/DB_ban_unban(var/ckey, var/bantype, var/job = "") if(!check_rights(R_MOD) && !check_rights(R_ADMIN)) return - establish_db_connection() - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) error("[key_name_admin(usr)] attempted to unban [ckey], but somehow server could not establish a database connection.") return @@ -139,21 +148,27 @@ datum/admins/proc/DB_ban_unban(var/ckey, var/bantype, var/job = "") else bantype_sql = "type = '[bantype_str]'" - var/DBQuery/query = dbcon.NewQuery("SELECT id FROM players WHERE ckey = '[ckey]'") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT id FROM [format_table_name("players")] WHERE ckey = :ckey", list("ckey" = ckey)) query.Execute() if(!query.NextRow()) error("[key_name_admin(usr)] attempted to unban [ckey], but [ckey] has not been seen yet.") return var/target_id = query.item[1] - var/sql = "SELECT id FROM bans WHERE target_id = [target_id] AND [bantype_sql] AND (unbanned is null OR unbanned = false)" + var/sql = "SELECT id FROM [format_table_name("bans")] WHERE target_id = :target_id AND [bantype_sql] AND (unbanned IS NULL OR unbanned = FALSE)" + + if(job) + sql += " AND job = :job" + + var/params = list("target_id" = target_id) if(job) - sql += " AND job = '[job]'" + params["job"] = job var/ban_id var/ban_number = 0 //failsafe - query = dbcon.NewQuery(sql) + query = SSdbcore.NewQuery(sql, params) + if(!query.Execute()) log_world("[key_name_admin(usr)] attempted to unban [ckey], but got error: [query.ErrorMsg()].") return @@ -182,8 +197,7 @@ datum/admins/proc/DB_ban_edit(var/banid = null, var/param = null) if(!check_rights(R_MOD) && !check_rights(R_ADMIN)) return - establish_db_connection() - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) error("[key_name_admin(usr)] attempted to edit ban record with id [banid], but somehow server could not establish a database connection.") return @@ -196,7 +210,7 @@ datum/admins/proc/DB_ban_edit(var/banid = null, var/param = null) var/duration var/reason - var/DBQuery/query = dbcon.NewQuery("SELECT target_id, duration, reason FROM bans WHERE id = [banid]") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT target_id, duration, reason FROM [format_table_name("bans")] WHERE id = :id", list("id" = banid)) query.Execute() if(query.NextRow()) target_id = query.item[1] @@ -206,7 +220,7 @@ datum/admins/proc/DB_ban_edit(var/banid = null, var/param = null) error("[key_name_admin(usr)] attempted to edit ban record with id [banid], but matching record does not exist in database.") return - query = dbcon.NewQuery("SELECT ckey FROM players WHERE id = [target_id]") + query = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("players")] WHERE id = :id", list("id" = target_id)) query.Execute() if(!query.NextRow()) error("[key_name_admin(usr)] attempted to edit [ckey]'s ban, but [ckey] has not been seen yet.") @@ -224,7 +238,7 @@ datum/admins/proc/DB_ban_edit(var/banid = null, var/param = null) if(!value) to_chat(usr, "Cancelled") return - var/DBQuery/update_query = dbcon.NewQuery("UPDATE bans SET reason = '[value]', WHERE id = [banid]") + var/datum/db_query/update_query = SSdbcore.NewQuery("UPDATE [format_table_name("bans")] SET reason = :reason WHERE id = :id", list("reason" = value, "id" = banid)) if(!update_query.Execute()) log_world("[key_name_admin(usr)] tried to edit ban for [ckey] but got error: [update_query.ErrorMsg()].") return @@ -236,7 +250,7 @@ datum/admins/proc/DB_ban_edit(var/banid = null, var/param = null) if(!isnum(value) || !value) to_chat(usr, "Cancelled") return - var/DBQuery/update_query = dbcon.NewQuery("UPDATE bans SET duration = [value], expiration_time = DATE_ADD(time, INTERVAL '[value]' MINUTE) WHERE id = [banid]") + var/datum/db_query/update_query = SSdbcore.NewQuery("UPDATE [format_table_name("bans")] SET duration = :duration, expiration_time = DATE_ADD(time, INTERVAL :duration MINUTE) WHERE id = :id", list("duration" = value, "id" = banid)) if(!update_query.Execute()) log_world("[key_name_admin(usr)] tried to edit a ban duration for [ckey] but got error: [update_query.ErrorMsg()].") return @@ -258,14 +272,13 @@ datum/admins/proc/DB_ban_unban_by_id(var/id) if(!check_rights(R_MOD) && !check_rights(R_ADMIN)) return - establish_db_connection() - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) error("[key_name_admin(usr)] attempted to remove ban record with id [id], but somehow server could not establish a database connection.") return var/ckey - var/DBQuery/query = dbcon.NewQuery("SELECT target_id FROM bans WHERE id = [id]") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT target_id FROM [format_table_name("bans")] WHERE id = :id", list("id" = id)) query.Execute() if(query.NextRow()) ckey = query.item[1] @@ -276,16 +289,14 @@ datum/admins/proc/DB_ban_unban_by_id(var/id) if(!src.owner || !istype(src.owner, /client)) return - query = dbcon.NewQuery("SELECT id FROM players WHERE ckey = '[usr.ckey]'") + query = SSdbcore.NewQuery("SELECT id FROM [format_table_name("players")] WHERE ckey = :ckey", list("ckey" = usr.ckey)) query.Execute() if(!query.NextRow()) error("[key_name_admin(usr)] attempted to remove ban record with id [id], but admin database record does not exist.") return var/admin_id = query.item[1] - var/sql_update = "UPDATE bans SET unbanned = 1, unbanned_time = Now(), unbanned_by_id = [admin_id] WHERE id = [id]" - - var/DBQuery/query_update = dbcon.NewQuery(sql_update) + var/datum/db_query/query_update = SSdbcore.NewQuery("UPDATE [format_table_name("bans")] SET unbanned = 1, unbanned_time = Now(), unbanned_by_id = :admin_id WHERE id = :id", list("admin_id" = admin_id, "id" = id)) if(!query_update.Execute()) log_world("[key_name_admin(usr)] tried to unban [ckey] but got error: [query_update.ErrorMsg()].") return @@ -310,8 +321,7 @@ datum/admins/proc/DB_ban_unban_by_id(var/id) if(!check_rights(R_MOD) && !check_rights(R_ADMIN)) return - establish_db_connection() - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) to_chat(usr, "\red Failed to establish database connection") return @@ -402,13 +412,13 @@ datum/admins/proc/DB_ban_unban_by_id(var/id) output += "" var/player_id - var/DBQuery/query = dbcon.NewQuery("SELECT id FROM players WHERE ckey='[playerckey]'") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT id FROM [format_table_name("players")] WHERE ckey = :ckey", list("ckey" = playerckey)) query.Execute() if(query.NextRow()) player_id = query.item[1] var/admin_id - query = dbcon.NewQuery("SELECT id FROM players WHERE ckey='[adminckey]'") + query = SSdbcore.NewQuery("SELECT id FROM [format_table_name("players")] WHERE ckey = :ckey", list("ckey" = adminckey)) query.Execute() if(query.NextRow()) admin_id = query.item[1] @@ -451,8 +461,9 @@ datum/admins/proc/DB_ban_unban_by_id(var/id) else bantypesearch += "'PERMABAN' " - - var/DBQuery/select_query = dbcon.NewQuery("SELECT id, time, type, reason, job, duration, expiration_time, target_id, banned_by_id, unbanned, unbanned_by_id, unbanned_time, ip, cid FROM bans WHERE 1 [playersearch] [adminsearch] [ipsearch] [cidsearch] [bantypesearch] ORDER BY time DESC LIMIT 100") + var/datum/db_query/select_query = SSdbcore.NewQuery( + "SELECT id, time, type, reason, job, duration, expiration_time, target_id, banned_by_id, unbanned, unbanned_by_id, unbanned_time, ip, cid \ + FROM [format_table_name("bans")] WHERE 1 [playersearch] [adminsearch] [ipsearch] [cidsearch] [bantypesearch] ORDER BY time DESC LIMIT 100") select_query.Execute() var/now = time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") // MUST BE the same format as SQL gives us the dates in, and MUST be least to most specific (i.e. year, month, day not day, month, year) @@ -476,17 +487,17 @@ datum/admins/proc/DB_ban_unban_by_id(var/id) var/banned_by_ckey var/unbanned_by_ckey - query = dbcon.NewQuery("SELECT ckey FROM players WHERE id = [target_id]") + query = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("players")] WHERE id = :id", list("id" = target_id)) query.Execute() if(query.NextRow()) target_ckey = query.item[1] - query = dbcon.NewQuery("SELECT ckey FROM players WHERE id = [banned_by_id]") + query = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("players")] WHERE id = :id", list("id" = banned_by_id)) query.Execute() if(query.NextRow()) banned_by_ckey = query.item[1] - query = dbcon.NewQuery("SELECT ckey FROM players WHERE id = [unbanned_by_id]") + query = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("players")] WHERE id = :id", list("id" = unbanned_by_id)) query.Execute() if(query.NextRow()) unbanned_by_ckey = query.item[1] diff --git a/code/modules/admin/DB_search/search.dm b/code/modules/admin/DB_search/search.dm index 3624a2c19ac..5a1814e21f9 100644 --- a/code/modules/admin/DB_search/search.dm +++ b/code/modules/admin/DB_search/search.dm @@ -2,24 +2,24 @@ var/datum/browser/panel var/empty = 1 -/datum/DB_search/verb/new_search_related(var/ckey as text) +/datum/DB_search/verb/new_search_related(ckey as text) set category = "Admin" set name = "Search related accounts" set desc = "Search players with same IP or CID" var/list/ip_related_ckeys = list() var/list/cid_related_ckeys = list() - var/DBQuery/search_query = dbcon.NewQuery("SELECT ip_related_ids, cid_related_ids FROM players WHERE ckey = '[sanitizeSQL(ckey)]'") + var/datum/db_query/search_query = SSdbcore.NewQuery("SELECT ip_related_ids, cid_related_ids FROM [format_table_name("players")] WHERE ckey = :ckey", list(ckey = ckey)) search_query.Execute() if(search_query.NextRow()) ip_related_ckeys = splittext(search_query.item[1], ",") cid_related_ckeys = splittext(search_query.item[2], ",") - search_query = dbcon.NewQuery("SELECT ckey FROM players WHERE id IN ([jointext(ip_related_ckeys, ",")])") + search_query = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("players")] WHERE id IN ([jointext(ip_related_ckeys, ",")])") search_query.Execute() ip_related_ckeys = list() while(search_query.NextRow()) ip_related_ckeys += search_query.item[1] - search_query = dbcon.NewQuery("SELECT ckey FROM players WHERE id IN ([jointext(cid_related_ckeys, ",")])") + search_query = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("players")] WHERE id IN ([jointext(cid_related_ckeys, ",")])") search_query.Execute() cid_related_ckeys = list() while(search_query.NextRow()) @@ -33,6 +33,7 @@ else to_chat(usr,"No player with ckey = [ckey] found.") + qdel(search_query) /datum/DB_search/verb/new_search() set category = "Admin" @@ -44,8 +45,7 @@ /datum/DB_search/proc/DB_players_search() - establish_db_connection() - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) to_chat(usr, "\red Failed to establish database connection") return @@ -113,10 +113,13 @@ hsrc.empty = 1 if(dbsearchckey_search || dbsearchip_search || dbsearchcid_search) hsrc.empty = 0 - var/DBQuery/search_query = dbcon.NewQuery("SELECT ckey, ip, cid, last_seen FROM players WHERE ckey = '[sanitizeSQL(dbsearchckey_search)]' OR ip = '[sanitizeSQL(dbsearchip_search)]' OR cid = '[sanitizeSQL(dbsearchcid_search)]'") - search_query.Execute() + var/datum/db_query/search_query = SSdbcore.NewQuery( + "SELECT ckey, ip, computerid, lastseen FROM [format_table_name("players")] WHERE ckey = :ckey OR ip = :ip OR computerid = :cid", + list(ckey = dbsearchckey_search, ip = dbsearchip_search, cid = dbsearchcid_search) + ) + search_query.warn_execute() while(search_query.NextRow()) output = "[search_query.item[1]][search_query.item[2]][search_query.item[3]][search_query.item[4]]" hsrc.panel.add_content(output) hsrc.panel.open() - return \ No newline at end of file + return diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 1304e699516..e801e80ae1e 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -37,13 +37,13 @@ world/IsBanned(key, address, computer_id, real_bans_only=FALSE) var/ckeytext = ckey(key) - if(!establish_db_connection()) + if(!SSdbcore.Connect()) error("Ban database connection failure. Key [ckeytext] not checked") log_misc("Ban database connection failure. Key [ckeytext] not checked") return var/id - var/DBQuery/get_id = dbcon.NewQuery("SELECT id FROM players WHERE ckey='[ckeytext]'") + var/datum/db_query/get_id = SSdbcore.NewQuery("SELECT id FROM [format_table_name("players")] WHERE ckey = :ckey", list("ckey" = ckeytext)) get_id.Execute() if(get_id.NextRow()) id = get_id.item[1] @@ -55,17 +55,17 @@ world/IsBanned(key, address, computer_id, real_bans_only=FALSE) var/cidquery = "" if(address) failedip = 0 - ipquery = " OR ip = '[address]' " + ipquery = " OR ip = :address " if(computer_id) failedcid = 0 - cidquery = " OR cid = '[computer_id]' " + cidquery = " OR cid = :computer_id " - var/DBQuery/query = dbcon.NewQuery(" \ + var/datum/db_query/query = SSdbcore.NewQuery(" \ SELECT target_id, banned_by_id, reason, expiration_time, duration, time, type \ FROM bans WHERE \ (\ - (target_id = '[id]' [ipquery] [cidquery]) \ + (target_id = :id [ipquery] [cidquery]) \ AND \ (type = 'PERMABAN' \ OR (\ @@ -73,7 +73,7 @@ world/IsBanned(key, address, computer_id, real_bans_only=FALSE) )\ ) \ AND isnull(unbanned)\ - )") + )", list("id" = id, "address" = address, "computer_id" = computer_id)) if(!query.Execute()) log_world("Trying to fetch ban record for [ckeytext] but got error: [query.ErrorMsg()].") @@ -89,13 +89,13 @@ world/IsBanned(key, address, computer_id, real_bans_only=FALSE) var/bantype = query.item[7] var/banned_ckey - var/DBQuery/get_banned_ckey = dbcon.NewQuery("SELECT ckey FROM players WHERE id=[target_id]") + var/datum/db_query/get_banned_ckey = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("players")] WHERE id = :id", list("id" = target_id)) get_banned_ckey.Execute() if(get_banned_ckey.NextRow()) banned_ckey = get_banned_ckey.item[1] var/banned_by_ckey - var/DBQuery/get_banned_by_ckey = dbcon.NewQuery("SELECT ckey FROM players WHERE id=[banned_by_id]") + var/datum/db_query/get_banned_by_ckey = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("players")] WHERE id = :id", list("id" = banned_by_id)) get_banned_by_ckey.Execute() if(get_banned_by_ckey.NextRow()) banned_by_ckey = get_banned_by_ckey.item[1] diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm index 48c32b6d50e..66c47674a53 100644 --- a/code/modules/admin/admin_ranks.dm +++ b/code/modules/admin/admin_ranks.dm @@ -107,8 +107,7 @@ var/list/admin_ranks = list() //list of all ranks with associated rights load_admins_legacy() return TRUE - establish_db_connection() - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) error("Failed to connect to database in load_admins(). Reverting to legacy system.") log_misc("Failed to connect to database in load_admins(). Reverting to legacy system.") load_admins_legacy() @@ -123,11 +122,11 @@ var/list/admin_ranks = list() //list of all ranks with associated rights config.admin_legacy_system = 1 load_admins_legacy() return FALSE - + return TRUE /proc/load_admins() - var/DBQuery/query = dbcon.NewQuery("SELECT ckey, rank, flags FROM players WHERE rank != 'player'") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey, rank, flags FROM [format_table_name("players")] WHERE rank != 'player'") query.Execute() while(query.NextRow()) var/ckey = query.item[1] @@ -152,11 +151,10 @@ var/list/admin_ranks = list() //list of all ranks with associated rights /proc/load_permissions(var/player_id) var/flag = 0 - establish_db_connection() - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) return flag - var/DBQuery/query = dbcon.NewQuery("SELECT fun, server, debug, permissions, mentor, moderator, admin, host FROM permissions WHERE player_id = [player_id]") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT fun, server, debug, permissions, mentor, moderator, admin, host FROM [format_table_name("permissions")] WHERE player_id = :player_id", list("player_id" = player_id)) if(!query.Execute()) return flag diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 69a9756c1be..eb5ac723d8a 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -42,6 +42,7 @@ GLOBAL_LIST_INIT(admin_verbs_server, list( /client/proc/ToRban, /client/proc/reload_admins, /client/proc/reload_mentors, + /client/proc/reestablish_db_connection, /client/proc/toggle_random_events)) GLOBAL_LIST_INIT(admin_verbs_debug, list( diff --git a/code/modules/admin/banjob.dm b/code/modules/admin/banjob.dm index 634de7c91e6..e99792db727 100644 --- a/code/modules/admin/banjob.dm +++ b/code/modules/admin/banjob.dm @@ -66,7 +66,7 @@ DEBUG jobban_keylist=list() log_admin("jobban_keylist was empty") else - if(!establish_db_connection()) + if(!SSdbcore.Connect()) error("Database connection failed. Reverting to the legacy ban system.") log_misc("Database connection failed. Reverting to the legacy ban system.") config.ban_legacy_system = 1 @@ -74,13 +74,13 @@ DEBUG return //Job permabans - var/DBQuery/perma_query = dbcon.NewQuery("SELECT target_id, job FROM bans WHERE type = 'JOB_PERMABAN' AND isnull(unbanned)") + var/datum/db_query/perma_query = SSdbcore.NewQuery("SELECT target_id, job FROM [format_table_name("bans")] WHERE type = 'JOB_PERMABAN' AND isnull(unbanned)") perma_query.Execute() while(perma_query.NextRow()) var/id = perma_query.item[1] var/job = perma_query.item[2] - var/DBQuery/get_ckey = dbcon.NewQuery("SELECT ckey from players WHERE id = '[id]'") + var/datum/db_query/get_ckey = SSdbcore.NewQuery("SELECT ckey from players WHERE id = :id", list("id" = id)) get_ckey.Execute() if(get_ckey.NextRow()) var/ckey = get_ckey.item[1] @@ -89,13 +89,13 @@ DEBUG //Job tempbans - var/DBQuery/query = dbcon.NewQuery("SELECT target_id, job FROM bans WHERE type = 'JOB_TEMPBAN' AND isnull(unbanned) AND expiration_time > Now()") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT target_id, job FROM [format_table_name("bans")] WHERE type = 'JOB_TEMPBAN' AND isnull(unbanned) AND expiration_time > Now()") query.Execute() while(query.NextRow()) var/id = query.item[1] var/job = query.item[2] - var/DBQuery/get_ckey = dbcon.NewQuery("SELECT ckey from players WHERE id = '[id]'") + var/datum/db_query/get_ckey = SSdbcore.NewQuery("SELECT ckey from players WHERE id = :id", list("id" = id)) get_ckey.Execute() if(get_ckey.NextRow()) var/ckey = get_ckey.item[1] diff --git a/code/modules/admin/permissionverbs/permissionedit.dm b/code/modules/admin/permissionverbs/permissionedit.dm index e5a71ad5c96..cab3aaa91dc 100644 --- a/code/modules/admin/permissionverbs/permissionedit.dm +++ b/code/modules/admin/permissionverbs/permissionedit.dm @@ -61,9 +61,7 @@ to_chat(usr, SPAN_WARNING("You do not have permission to do this!")) return - establish_db_connection() - - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) to_chat(usr, SPAN_WARNING("Failed to establish database connection.")) return @@ -78,22 +76,27 @@ if(!istext(admin_ckey) || !istext(new_rank)) return - var/DBQuery/select_query = dbcon.NewQuery("SELECT ckey FROM players WHERE ckey = '[admin_ckey]' AND rank != 'player'") + var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("players")] WHERE ckey = :ckey AND rank != 'player'", list("ckey" = admin_ckey)) select_query.Execute() var/new_admin = TRUE if(select_query.NextRow()) new_admin = FALSE + var/datum/db_query/insert_query = SSdbcore.NewQuery( + "UPDATE [format_table_name("players")] SET rank = :rank WHERE ckey = :ckey", + list( + "rank" = new_rank, + "ckey" = admin_ckey, + ) + ) + insert_query.Execute() + if(new_admin) - var/DBQuery/insert_query = dbcon.NewQuery("UPDATE players SET rank = '[new_rank]' WHERE ckey = '[admin_ckey]'") - insert_query.Execute() message_admins("[key_name_admin(usr)] made [key_name_admin(admin_ckey)] an admin with the rank [new_rank]") log_admin("[key_name(usr)] made [key_name(admin_ckey)] an admin with the rank [new_rank]") to_chat(usr, SPAN_NOTICE("New admin added.")) else - var/DBQuery/insert_query = dbcon.NewQuery("UPDATE players SET rank = '[new_rank]' WHERE ckey = '[admin_ckey]'") - insert_query.Execute() message_admins("[key_name_admin(usr)] changed [key_name_admin(admin_ckey)] admin rank to [new_rank]") log_admin("[key_name(usr)] changed [key_name(admin_ckey)] admin rank to [new_rank]") to_chat(usr, SPAN_NOTICE("Admin rank changed.")) @@ -109,8 +112,7 @@ to_chat(usr, SPAN_WARNING("You do not have permission to do this!")) return - establish_db_connection() - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) to_chat(usr, SPAN_WARNING("Failed to establish database connection.")) return @@ -128,7 +130,7 @@ if(!istext(admin_ckey) || !isnum(new_permission)) return - var/DBQuery/select_query = dbcon.NewQuery("SELECT ckey, flags FROM players WHERE ckey = '[admin_ckey]'") + var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT ckey, flags FROM [format_table_name("players")] WHERE ckey = :ckey", list("ckey" = admin_ckey)) select_query.Execute() if(!select_query.NextRow()) to_chat(usr, SPAN_WARNING("Permissions edit for [admin_ckey] failed on retrieving related database record.")) @@ -137,13 +139,13 @@ var/admin_rights = text2num(select_query.item[2]) if(admin_rights & new_permission) //This admin already has this permission, so we are removing it. - var/DBQuery/insert_query = dbcon.NewQuery("UPDATE players SET flags = [admin_rights & ~new_permission] WHERE ckey = '[admin_ckey]'") + var/datum/db_query/insert_query = SSdbcore.NewQuery("UPDATE [format_table_name("players")] SET flags = :flags WHERE ckey = :ckey", list("flags" = admin_rights & ~new_permission, ckey = admin_ckey)) insert_query.Execute() message_admins("[key_name_admin(usr)] removed the [nominal] permission of [admin_ckey]") log_admin("[key_name(usr)] removed the [nominal] permission of [admin_ckey]") to_chat(usr, SPAN_NOTICE("Permission removed.")) else //This admin doesn't have this permission, so we are adding it. - var/DBQuery/insert_query = dbcon.NewQuery("UPDATE players SET flags = '[admin_rights | new_permission]' WHERE ckey = '[admin_ckey]'") + var/datum/db_query/insert_query = SSdbcore.NewQuery("UPDATE [format_table_name("players")] SET flags = :flags WHERE ckey = :ckey", list("flags" = admin_rights | new_permission, ckey = admin_ckey)) insert_query.Execute() message_admins("[key_name_admin(usr)] added the [nominal] permission of [admin_ckey]") log_admin("[key_name(usr)] added the [nominal] permission of [admin_ckey]") diff --git a/code/modules/admin/verbs/antiraid.dm b/code/modules/admin/verbs/antiraid.dm index 00adde49b4e..a37560e236d 100644 --- a/code/modules/admin/verbs/antiraid.dm +++ b/code/modules/admin/verbs/antiraid.dm @@ -14,14 +14,14 @@ GLOBAL_LIST_EMPTY(PB_bypass) //Handles ckey config.panic_bunker = (!config.panic_bunker) log_and_message_admins("[key_name(usr)] has toggled the Panic Bunker, it is now [(config.panic_bunker?"on":"off")].") - if (config.panic_bunker && (!dbcon || !dbcon.IsConnected())) + if (config.panic_bunker && !SSdbcore.Connect()) message_admins("The database is not connected! Panic bunker will not work until the connection is reestablished.") /client/proc/addbunkerbypass(ckeytobypass as text) set category = "Server" set name = "Add PB Bypass" set desc = "Allows a given ckey to connect despite the panic bunker for a given round." - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) to_chat(usr, "The Database is not enabled or not working!") return @@ -33,7 +33,7 @@ GLOBAL_LIST_EMPTY(PB_bypass) //Handles ckey set category = "Server" set name = "Revoke PB Bypass" set desc = "Revoke's a ckey's permission to bypass the panic bunker for a given round." - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) to_chat(usr, "The Database is not enabled or not working!") return @@ -51,7 +51,7 @@ GLOBAL_LIST_EMPTY(PB_bypass) //Handles ckey config.paranoia_logging = (!config.paranoia_logging) log_and_message_admins("[key_name(usr)] has toggled Paranoia Logging, it is now [(config.paranoia_logging?"on":"off")].") - if (config.paranoia_logging && (!dbcon || !dbcon.IsConnected())) + if (config.paranoia_logging && !SSdbcore.Connect()) message_admins("The database is not connected! Paranoia logging will not be able to give 'player age' (time since first connection) warnings, only Byond account warnings.") /client/proc/ip_reputation() @@ -64,26 +64,26 @@ GLOBAL_LIST_EMPTY(PB_bypass) //Handles ckey config.ip_reputation = (!config.ip_reputation) log_and_message_admins("[key_name(usr)] has toggled IP reputation checks, it is now [(config.ip_reputation?"on":"off")].") - if (config.ip_reputation && (!dbcon || !dbcon.IsConnected())) + if (config.ip_reputation && !SSdbcore.Connect()) message_admins("The database is not connected! IP reputation logging will not be able to allow existing players to bypass the reputation checks (if that is enabled).") -/client/proc/toggle_vpn_white(var/ckey as text) +/client/proc/toggle_vpn_white(ckey as text) set category = "Server" set name = "Whitelist ckey from VPN Checks" if(!check_rights(R_ADMIN)) return - if (!dbcon || !dbcon.IsConnected()) + if (!SSdbcore.IsConnected()) to_chat(usr,"The database is not connected!") return - var/DBQuery/query = dbcon.NewQuery("SELECT id FROM players WHERE ckey = '[sanitizeSQL(ckey)]'") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT id FROM [format_table_name("players")] WHERE ckey = :ckey", list("ckey" = ckey)) query.Execute() if(query.NextRow()) var/temp_id = query.item[1] log_and_message_admins("[key_name(usr)] has toggled VPN checks for [ckey].") - query = dbcon.NewQuery("UPDATE players SET VPN_check_white = !VPN_check_white WHERE id = '[temp_id]'") + query = SSdbcore.NewQuery("UPDATE [format_table_name("players")] SET VPN_check_white = !VPN_check_white WHERE id = :temp_id", list("temp_id" = temp_id)) query.Execute() else to_chat(usr,"Player [ckey] not found!") diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 05d12f7d9c5..94679b4de46 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -335,8 +335,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( holder.owner = null admins -= src QDEL_NULL(tooltips) - if(dbcon.IsConnected()) - var/DBQuery/query = dbcon.NewQuery("UPDATE players SET last_seen = Now() WHERE id = [src.id]") + if(SSdbcore.IsConnected()) + var/datum/db_query/query = SSdbcore.NewQuery("UPDATE [format_table_name("players")] SET lastseen = Now() WHERE ckey = :ckey", list("ckey" = ckey)) if(!query.Execute()) log_world("Failed to update players table for user with id [src.id]. Error message: [query.ErrorMsg()].") Master.UpdateTickRate() @@ -399,20 +399,33 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( /client/proc/register_in_db() // Prevents the crash if the DB isn't connected. - if(!dbcon.IsConnected()) + if(!SSdbcore.IsConnected()) return registration_date = src.get_registration_date() src.get_country() src.get_byond_age() // Get days since byond join - var/DBQuery/query_insert = dbcon.NewQuery("INSERT INTO players (ckey, first_seen, last_seen, registered, ip, cid, rank, byond_version, country) VALUES ('[src.ckey]', Now(), Now(), '[registration_date]', '[sql_sanitize_text(src.address)]', '[sql_sanitize_text(src.computer_id)]', 'player', [src.byond_version], '[src.country_code]')") + var/datum/db_query/query_insert = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("players")] \ + (ckey, first_seen, last_seen, registered, ip, cid, rank, byond_version, country) \ + VALUES (:ckey, Now(), Now(), :registered, :ip, :cid, 'player', :byond_version, :country)", + list( + "ckey" = src.ckey, + "registered" = registration_date, + "ip" = src.address, + "cid" = src.computer_id, + "byond_version" = src.byond_version, + "country" = src.country_code + ) +) + if(!query_insert.Execute()) log_world("##CRITICAL: Failed to create player record for user [ckey]. Error message: [query_insert.ErrorMsg()].") return else - var/DBQuery/get_player_id = dbcon.NewQuery("SELECT id, first_seen FROM players WHERE ckey = '[src.ckey]'") + var/datum/db_query/get_player_id = SSdbcore.NewQuery("SELECT id, first_seen FROM [format_table_name("players")] WHERE ckey = :ckey", list("ckey" = src.ckey)) get_player_id.Execute() if(get_player_id.NextRow()) src.id = get_player_id.item[1] @@ -420,9 +433,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( //Not actually age, but rather time since first seen in days /client/proc/get_player_age() - if(first_seen && dbcon.IsConnected()) - var/dateSQL = sanitizeSQL(first_seen) - var/DBQuery/query_datediff = dbcon.NewQuery("SELECT DATEDIFF(Now(),'[dateSQL]')") + if(first_seen && SSdbcore.IsConnected()) + var/datum/db_query/query_datediff = SSdbcore.NewQuery("SELECT DATEDIFF(Now(), :firstseen)", list("firstseen" = first_seen)) if(query_datediff.Execute() && query_datediff.NextRow()) src.first_seen_days_ago = text2num(query_datediff.item[1]) @@ -434,9 +446,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( /client/proc/get_byond_age() if(!registration_date) get_registration_date() - if(registration_date && dbcon.IsConnected()) - var/dateSQL = sanitizeSQL(registration_date) - var/DBQuery/query_datediff = dbcon.NewQuery("SELECT DATEDIFF(Now(),'[dateSQL]')") + if(registration_date && SSdbcore.IsConnected()) + var/datum/db_query/query_datediff = SSdbcore.NewQuery("SELECT DATEDIFF(Now(),:reg_date)", list("reg_date" = registration_date)) if(query_datediff.Execute() && query_datediff.NextRow()) src.account_age_in_days = text2num(query_datediff.item[1]) if(config.paranoia_logging && isnum(src.account_age_in_days) && src.account_age_in_days <= 2) @@ -447,17 +458,16 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if(IsGuestKey(src.key)) return - establish_db_connection() - if(dbcon.IsConnected()) + if(SSdbcore.Connect()) // Get existing player from DB - var/DBQuery/query = dbcon.NewQuery("SELECT id from players WHERE ckey = '[src.ckey]'") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT id from players WHERE ckey = :ckey", list("ckey" = src.ckey)) if(!query.Execute()) log_world("Failed to get player record for user with ckey '[src.ckey]'. Error message: [query.ErrorMsg()].") // Not their first time here else if(query.NextRow()) // client already registered so we fetch all needed data - query = dbcon.NewQuery("SELECT id, registered, first_seen, VPN_check_white FROM players WHERE id = [query.item[1]]") + query = SSdbcore.NewQuery("SELECT id, registered, first_seen, VPN_check_white FROM [format_table_name("players")] WHERE id = :id", list("id" = query.item[1])) query.Execute() if(query.NextRow()) src.id = query.item[1] @@ -467,7 +477,23 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( src.get_country() //Player already identified previously, we need to just update the 'lastseen', 'ip' and 'computer_id' variables - var/DBQuery/query_update = dbcon.NewQuery("UPDATE players SET last_seen = Now(), ip = '[src.address]', cid = '[src.computer_id]', byond_version = '[src.byond_version]', country = '[src.country_code]' WHERE id = [src.id]") + var/datum/db_query/query_update = SSdbcore.NewQuery( + "UPDATE [format_table_name("players")] \ + SET last_seen = Now(), \ + ip = :ip, \ + cid = :cid, \ + byond_version = :byond_version, \ + country = :country \ + WHERE id = :id", + list( + "ip" = src.address, + "cid" = src.computer_id, + "byond_version" = src.byond_version, + "country" = src.country_code, + "id" = src.id + ) + ) + if(!query_update.Execute()) log_world("Failed to update players table for user with id [src.id]. Error message: [query_update.ErrorMsg()].") @@ -479,12 +505,19 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( to_chat(src, "Sorry but the server is currently not accepting connections from never before seen players.") del(src) // Hard del the client. This terminates the connection. return 0 - query = dbcon.NewQuery("SELECT ip_related_ids, cid_related_ids FROM players WHERE id = '[src.id]'") + query = SSdbcore.NewQuery("SELECT ip_related_ids, cid_related_ids FROM [format_table_name("players")] WHERE id = :id", list("id" = src.id)) query.Execute() if(query.NextRow()) related_ip = splittext(query.item[1], ",") related_cid = splittext(query.item[2], ",") - query = dbcon.NewQuery("SELECT id, ip, cid FROM players WHERE (ip = '[address]' OR cid = '[computer_id]') AND id <> '[src.id]'") + query = SSdbcore.NewQuery( + "SELECT id, ip, cid FROM [format_table_name("players")] WHERE (ip = :ip OR cid = :cid) AND id <> :id", + list( + "ip" = address, + "cid" = computer_id, + "id" = src.id + ) + ) query.Execute() var/changed = 0 while(query.NextRow()) @@ -498,7 +531,14 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( changed = 1 related_cid |= temp_id if(changed) - query = dbcon.NewQuery("UPDATE players SET cid_related_ids = '[jointext(related_cid, ",")]', ip_related_ids = '[jointext(related_ip, ",")]' WHERE id = '[src.id]'") + query = SSdbcore.NewQuery( + "UPDATE [format_table_name("players")] SET cid_related_ids = :cid_related, ip_related_ids = :ip_related WHERE id = :id", + list( + "cid_related" = jointext(related_cid, ","), + "ip_related" = jointext(related_ip, ","), + "id" = src.id + ), + ) query.Execute() diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm index 0b8804feec6..b3cca071f2b 100644 --- a/code/modules/library/lib_machines.dm +++ b/code/modules/library/lib_machines.dm @@ -11,7 +11,7 @@ /* * Borrowbook datum */ -datum/borrowbook // Datum used to keep track of who has borrowed what when and for how long. +/datum/borrowbook // Datum used to keep track of who has borrowed what when and for how long. var/bookname var/mobname var/getdate @@ -32,19 +32,18 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f var/author var/SQLquery -/obj/machinery/librarypubliccomp/attack_hand(var/mob/user as mob) +/obj/machinery/librarypubliccomp/attack_hand(mob/user as mob) usr.set_machine(src) - var/dat = "Library Visitor\n" // + var/dat = "" // switch(screenstate) if(0) dat += {"

Search Settings


- Filter by Title: [title]
- Filter by Category: [category]
- Filter by Author: [author]
- \[Start Search\]
"} + Filter by Title: [title]
+ Filter by Category: [category]
+ Filter by Author: [author]
+ \[Start Search\]
"} if(1) - establish_db_connection() - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance.
" else if(!SQLquery) dat += "ERROR: Malformed search request. Please contact your system administrator for assistance.
" @@ -52,7 +51,7 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f dat += {""} - var/DBQuery/query = dbcon.NewQuery(SQLquery) + var/datum/db_query/query = SSdbcore.NewQuery(SQLquery) query.Execute() while(query.NextRow()) @@ -78,23 +77,20 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f title = sanitize(newtitle) else title = null - title = sanitizeSQL(title) if(href_list["setcategory"]) var/newcategory = input("Choose a category to search for:") in list("Any", "Fiction", "Non-Fiction", "Adult", "Reference", "Religion") if(newcategory) category = sanitize(newcategory) else category = "Any" - category = sanitizeSQL(category) if(href_list["setauthor"]) var/newauthor = input("Enter an author to search for:") as text|null if(newauthor) author = sanitize(newauthor) else author = null - author = sanitizeSQL(author) if(href_list["search"]) - SQLquery = "SELECT author, title, category, id FROM library WHERE " + SQLquery = "SELECT author, title, category, id FROM [format_table_name("library")] WHERE " if(category == "Any") SQLquery += "author LIKE '%[author]%' AND title LIKE '%[title]%'" else @@ -132,25 +128,25 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f var/bibledelay = 0 // LOL NO SPAM (1 minute delay) -- Doohl -/obj/machinery/librarycomp/attack_hand(var/mob/user as mob) +/obj/machinery/librarycomp/attack_hand(mob/user as mob) usr.set_machine(src) var/dat = "Book Inventory Management\n" // switch(screenstate) if(0) // Main Menu - dat += {"1. View General Inventory
- 2. View Checked Out Inventory
- 3. Check out a Book
- 4. Connect to External Archive
- 5. Upload New Title to Archive
"} + dat += {"1. View General Inventory
+ 2. View Checked Out Inventory
+ 3. Check out a Book
+ 4. Connect to External Archive
+ 5. Upload New Title to Archive
"} if(src.emagged) - dat += "6. Access the Forbidden Lore Vault
" + dat += "6. Access the Forbidden Lore Vault
" if(1) // Inventory dat += "

Inventory


" for(var/obj/item/book/b in inventory) - dat += "[b.name] (Delete)
" - dat += "(Return to main menu)
" + dat += "[b.name] (Delete)
" + dat += "(Return to main menu)
" if(2) // Checked Out dat += "

Checked Out Books


" @@ -167,30 +163,29 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f else timedue = round(timedue) dat += {"\"[b.bookname]\", Checked out to: [b.mobname]
--- Taken: [timetaken] minutes ago, Due: in [timedue] minutes
- (Check In)

"} - dat += "(Return to main menu)
" + (Check In)

"} + dat += "(Return to main menu)
" if(3) // Check Out a Book dat += {"

Check Out a Book


Book: [src.buffer_book] - \[Edit\]
+ \[Edit\]
Recipient: [src.buffer_mob] - \[Edit\]
+ \[Edit\]
Checkout Date : [world.time/600]
Due Date: [(world.time + checkoutperiod)/600]
- (Checkout Period: [checkoutperiod] minutes) (+/-) - (Commit Entry)
- (Return to main menu)
"} + (Checkout Period: [checkoutperiod] minutes) (+/-) + (Commit Entry)
+ (Return to main menu)
"} if(4) dat += "

External Archive

" - establish_db_connection() - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance." else - dat += {"(Order book by SS13BN)

+ dat += {"(Order book by SS13BN)

AUTHORTITLECATEGORYSS13BN
- "} + var/datum/db_query/query = SSdbcore.NewQuery("SELECT id, author, title, category FROM [format_table_name("library")] ORDER BY :sortby", list("sortby" = sortby)) query.Execute() while(query.NextRow()) @@ -198,9 +193,9 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f var/author = query.item[2] var/title = query.item[3] var/category = query.item[4] - dat += "" + dat += "" dat += "
TITLEAUTHORCATEGORY
[author][title][category]\[Order\]
[author][title][category]\[Order\]
" - dat += "
(Return to main menu)
" + dat += "
(Return to main menu)
" if(5) dat += "

Upload a New Title

" if(!scanner) @@ -216,21 +211,21 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f Title: [scanner.cache.name]
"} if(!scanner.cache.author) scanner.cache.author = "Anonymous" - dat += {"Author: [scanner.cache.author]
- Category: [upload_category]
- \[Upload\]
"} - dat += "(Return to main menu)
" + dat += {"Author: [scanner.cache.author]
+ Category: [upload_category]
+ \[Upload\]
"} + dat += "(Return to main menu)
" if(6) dat += {"

Accessing Forbidden Lore Vault v 1.3

Are you absolutely sure you want to proceed? EldritchTomes Inc. takes no responsibilities for loss of sanity resulting from this action.

- Yes.
- No.
"} + Yes.
+ No.
"} //dat += "Close

" user << browse(dat, "window=library") onclose(user, "library") -/obj/machinery/librarycomp/emag_act(var/remaining_charges, var/mob/user) +/obj/machinery/librarycomp/emag_act(remaining_charges, mob/user) if (src.density && !src.emagged) src.emagged = 1 return 1 @@ -240,7 +235,7 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f var/obj/item/barcodescanner/scanner = W scanner.computer = src to_chat(user, "[scanner]'s associated machine has been set to [src].") - for (var/mob/V in hearers(src)) + for (var/mob/V in hearers(get_turf(src))) V.show_message("[src] lets out a low, short blip.", 2) else ..() @@ -275,7 +270,7 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f bibledelay = 0 else - for (var/mob/V in hearers(src)) + for (var/mob/V in hearers(get_turf(src))) V.show_message("[src]'s monitor flashes, \"Bible printer currently unavailable, please wait a moment.\"") if("7") @@ -319,28 +314,30 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f if(scanner.cache.unique) alert("This book has been rejected from the database. Aborting!") else - establish_db_connection() - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) alert("Connection to Archive has been severed. Aborting.") else /* - var/sqltitle = dbcon.Quote(scanner.cache.name) - var/sqlauthor = dbcon.Quote(scanner.cache.author) - var/sqlcontent = dbcon.Quote(scanner.cache.dat) - var/sqlcategory = dbcon.Quote(upload_category) + var/sqltitle = SSdbcore.Quote(scanner.cache.name) + var/sqlauthor = SSdbcore.Quote(scanner.cache.author) + var/sqlcontent = SSdbcore.Quote(scanner.cache.dat) + var/sqlcategory = SSdbcore.Quote(upload_category) */ - var/sqltitle = sanitizeSQL(scanner.cache.name) - var/sqlauthor = sanitizeSQL(scanner.cache.author) - var/sqlcontent = sanitizeSQL(scanner.cache.dat) - var/sqlcategory = sanitizeSQL(upload_category) + var/sqltitle = scanner.cache.name + var/sqlauthor = scanner.cache.author + var/sqlcontent = scanner.cache.dat + var/sqlcategory = upload_category var/author_id = null - var/DBQuery/get_author_id = dbcon.NewQuery("SELECT id FROM players WHERE ckey='[usr.ckey]'") + var/datum/db_query/get_author_id = SSdbcore.NewQuery("SELECT id FROM [format_table_name("players")] WHERE ckey = :ckey", list("ckey" = usr.ckey)) get_author_id.Execute() if(get_author_id.NextRow()) author_id = get_author_id.item[1] - var/DBQuery/query = dbcon.NewQuery("INSERT INTO library (author, title, content, category, author_id) VALUES ('[sqlauthor]', '[sqltitle]', '[sqlcontent]', '[sqlcategory]', [author_id])") + var/datum/db_query/query = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("library")] (author, title, content, category, author_id) VALUES (:sqlauthor, :sqltitle, :sqlcontent, :sqlcategory, :author_id)", + list("sqlauthor" = sqlauthor, "sqltitle" = sqltitle, "sqlcontent" = sqlcontent, "sqlcategory" = sqlcategory,"author_id" = author_id) + ) if(!query.Execute()) to_chat(usr, query.ErrorMsg()) else @@ -349,18 +346,17 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f alert("Upload Complete.") if(href_list["targetid"]) - var/sqlid = sanitizeSQL(href_list["targetid"]) - establish_db_connection() - if(!dbcon.IsConnected()) + var/sqlid = href_list["targetid"] + if(!SSdbcore.Connect()) alert("Connection to Archive has been severed. Aborting.") if(bibledelay) - for (var/mob/V in hearers(src)) + for (var/mob/V in hearers(get_turf(src))) V.show_message("[src]'s monitor flashes, \"Printer unavailable. Please allow a short time before attempting to print.\"") else bibledelay = 1 spawn(60) bibledelay = 0 - var/DBQuery/query = dbcon.NewQuery("SELECT * FROM library WHERE id=[sqlid]") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT * FROM [format_table_name("library")] WHERE id = :sqlid", list("sqlid" = sqlid)) query.Execute() while(query.NextRow()) @@ -397,21 +393,21 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f density = TRUE var/obj/item/book/cache // Last scanned book -/obj/machinery/libraryscanner/attackby(var/obj/O as obj, var/mob/user as mob) +/obj/machinery/libraryscanner/attackby(obj/O as obj, mob/user as mob) if(istype(O, /obj/item/book)) user.drop_item() O.loc = src -/obj/machinery/libraryscanner/attack_hand(var/mob/user as mob) +/obj/machinery/libraryscanner/attack_hand(mob/user as mob) usr.set_machine(src) var/dat = "Scanner Control Interface\n" // if(cache) dat += "Data stored in memory.
" else dat += "No data stored in memory.
" - dat += "\[Scan\]" + dat += "\[Scan\]" if(cache) - dat += " \[Clear Memory\]

\[Remove Book\]" + dat += " \[Clear Memory\]

\[Remove Book\]" else dat += "
" user << browse(dat, "window=scanner") @@ -446,7 +442,7 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f anchored = TRUE density = TRUE -/obj/machinery/bookbinder/attackby(var/obj/O as obj, var/mob/user as mob) +/obj/machinery/bookbinder/attackby(obj/O as obj, mob/user as mob) if(istype(O, /obj/item/paper)) user.drop_item() O.loc = src diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index 4fae2968ad0..ac937fba86c 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -46,13 +46,22 @@ output += "

Observe

" if(!IsGuestKey(src.key)) - establish_db_connection() - if(dbcon.IsConnected()) + if(SSdbcore.Connect()) var/isadmin = FALSE if(src.client && src.client.holder) isadmin = TRUE // TODO: reimplement database interaction - var/DBQuery/query = dbcon.NewQuery("SELECT id FROM erro_poll_question WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM erro_poll_vote WHERE ckey = \"[ckey]\") AND id NOT IN (SELECT pollid FROM erro_poll_textreply WHERE ckey = \"[ckey]\")") + var/datum/db_query/query = SSdbcore.NewQuery( + "SELECT id FROM erro_poll_question \ + WHERE [(isadmin ? "" : "adminonly = false AND")] \ + Now() BETWEEN starttime AND endtime \ + AND id NOT IN (SELECT pollid FROM [format_table_name("erro_poll_vote")] WHERE ckey = :ckey) \ + AND id NOT IN (SELECT pollid FROM [format_table_name("erro_poll_textreply")] WHERE ckey = :ckey)", + list( + "ckey" = ckey, + ) + ) + query.Execute() var/newpoll = FALSE while(query.NextRow()) diff --git a/code/modules/mob/new_player/poll.dm b/code/modules/mob/new_player/poll.dm index aab4cdde8dd..c7a12bc03a5 100644 --- a/code/modules/mob/new_player/poll.dm +++ b/code/modules/mob/new_player/poll.dm @@ -3,9 +3,8 @@ var/text /mob/new_player/proc/handle_player_polling() - establish_db_connection() - if(dbcon.IsConnected()) - var/DBQuery/select_query = dbcon.NewQuery("SELECT id, question FROM polls WHERE Now() BETWEEN start AND end") + if(SSdbcore.Connect()) + var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT id, question FROM [format_table_name("polls")] WHERE Now() BETWEEN start AND end") if(!select_query.Execute()) log_world("Failed to retrieve active player polls. Error message: [select_query.ErrorMsg()].") return @@ -36,10 +35,12 @@ if(poll_id == -1) return - establish_db_connection() - if(dbcon.IsConnected()) + if(SSdbcore.Connect()) - var/DBQuery/select_query = dbcon.NewQuery("SELECT start, end, question, type, FROM polls WHERE id = [poll_id]") + var/datum/db_query/select_query = SSdbcore.NewQuery( + "SELECT start, end, question, type, FROM [format_table_name("polls")] WHERE id = :poll_id", + list("poll_id" = poll_id) + ) if(!select_query.Execute()) log_world("Failed to get poll with id [poll_id]. Error message: [select_query.ErrorMsg()].") return @@ -61,7 +62,10 @@ switch(type) //Polls that have enumerated options if("OPTION") - var/DBQuery/voted_query = dbcon.NewQuery("SELECT option_id FROM poll_votes WHERE poll_id = [poll_id] AND player_id = [client.id]") + var/datum/db_query/voted_query = SSdbcore.NewQuery( + "SELECT option_id FROM [format_table_name("poll_votes")] WHERE poll_id = :poll_id AND ckey = :ckey", + list("ckey" = client.ckey, "poll_id" = poll_id) + ) if(!voted_query.Execute()) log_world("Failed to retrieve votes from poll [poll_id] for player [client.id]. Error message: [voted_query.ErrorMsg()].") return @@ -75,7 +79,9 @@ var/list/datum/poll_option/options = list() - var/DBQuery/options_query = dbcon.NewQuery("SELECT id, text FROM poll_options WHERE poll_id = [poll_id]") + var/datum/db_query/options_query = SSdbcore.NewQuery( + "SELECT id, text FROM [format_table_name("poll_options")] WHERE poll_id = :poll_id", + list("poll_id" = poll_id)) if(!options_query.Execute()) log_world("Failed to get poll options for poll with id [poll_id]. Error message: [options_query.ErrorMsg()].") return @@ -118,7 +124,10 @@ //Polls with a text input if("TEXT") - var/DBQuery/voted_query = dbcon.NewQuery("SELECT text FROM poll_text_replies WHERE poll_id = [poll_id] AND player_id = [client.id]") + var/datum/db_query/voted_query = SSdbcore.NewQuery( + "SELECT text FROM [format_table_name("poll_text_replies")] WHERE poll_id = :poll_id AND ckey = :ckey", + list("poll_id" = poll_id, "ckey" = client.ckey) + ) if(!voted_query.Execute()) log_world("Failed to get votes from text poll [poll_id] for user [client.id]. Error message: [voted_query.ErrorMsg()].") return @@ -167,10 +176,12 @@ if(!isnum(poll_id) || !isnum(option_id)) return - establish_db_connection() - if(dbcon.IsConnected()) + if(SSdbcore.Connect()) - var/DBQuery/select_query = dbcon.NewQuery("SELECT start, end, question, type, FROM polls WHERE id = [poll_id] AND Now() BETWEEN start AND end") + var/datum/db_query/select_query = SSdbcore.NewQuery( + "SELECT start, end, question, type, FROM [format_table_name("polls")] WHERE id = :poll_id AND Now() BETWEEN start AND end", + list("poll_id" = poll_id) + ) if(!select_query.Execute()) log_world("Failed to get poll [poll_id]. Error message: [select_query.ErrorMsg()].") return @@ -183,7 +194,10 @@ to_chat(usr, SPAN_DANGER("Poll not found.")) return - var/DBQuery/select_query2 = dbcon.NewQuery("SELECT id FROM poll_options WHERE id = [option_id] AND poll_id = [poll_id]") + var/datum/db_query/select_query2 = SSdbcore.NewQuery( + "SELECT id FROM [format_table_name("poll_options")] WHERE id = :option_id AND poll_id = :poll_id", + list("option_id" = option_id, "poll_id" = poll_id) + ) if(!select_query2.Execute()) log_world("Failed to get poll options for poll [poll_id]. Error message: [select_query2.ErrorMsg()].") return @@ -192,7 +206,10 @@ to_chat(usr, SPAN_WARNING("Invalid poll options.")) return - var/DBQuery/voted_query = dbcon.NewQuery("SELECT id FROM poll_votes WHERE poll_id = [poll_id] AND player_id = [client.id]") + var/datum/db_query/voted_query = SSdbcore.NewQuery( + "SELECT id FROM [format_table_name("poll_votes")] WHERE poll_id = :poll_id AND ckey = :ckey", + list("poll_id" = poll_id, "ckey" = client.ckey) + ) if(!voted_query.Execute()) log_world("Failed to get votes for poll [poll_id]. Error message: [voted_query.ErrorMsg()].") return @@ -201,7 +218,10 @@ to_chat(usr, SPAN_WARNING("You already voted in this poll.")) return - var/DBQuery/insert_query = dbcon.NewQuery("INSERT INTO poll_votes (time, option_id, poll_id, player_id) VALUES (Now(), [option_id], [poll_id], [client.id])") + var/datum/db_query/insert_query = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("poll_votes")] (time, option_id, poll_id, ckey) VALUES (Now(), :option_id, :poll_id, :ckey)", + list("option_id" = option_id, "poll_id" = poll_id, "ckey" = client.ckey) + ) if(!insert_query.Execute()) log_world("Failed to insert vote from [client.id] for poll [poll_id]. Error message: [insert_query.ErrorMsg()].") return @@ -216,10 +236,11 @@ if(!isnum(poll_id) || !istext(reply_text)) return - establish_db_connection() - if(dbcon.IsConnected()) + if(SSdbcore.Connect()) - var/DBQuery/select_query = dbcon.NewQuery("SELECT start, end, question, type FROM polls WHERE id = [poll_id] AND Now() BETWEEN start AND end") + var/datum/db_query/select_query = SSdbcore.NewQuery( + "SELECT start, end, question, type FROM [format_table_name("polls")] WHERE id = :id AND Now() BETWEEN start AND end", + list("id" = poll_id)) if(!select_query.Execute()) log_world("Failed to get poll [poll_id]. Error message: [select_query.ErrorMsg()].") return @@ -228,7 +249,9 @@ to_chat(usr, SPAN_WARNING("Invalid poll type.")) return - var/DBQuery/voted_query = dbcon.NewQuery("SELECT id FROM poll_text_replies WHERE poll_id = [poll_id] AND player_id = [client.id]") + var/datum/db_query/voted_query = SSdbcore.NewQuery( + "SELECT id FROM [format_table_name("poll_text_replies")] WHERE poll_id = :id AND player_id = :player_id", + list("id" = poll_id, "player_id" = client.id)) if(!voted_query.Execute()) log_world("Failed to get text replies for poll [poll_id] from user [client.id]. Error message: [voted_query.ErrorMsg()].") return @@ -246,7 +269,15 @@ to_chat(usr, SPAN_WARNING("The text you entered was blank, contained illegal characters or was too long. Please correct the text and submit again.")) return - var/DBQuery/insert_query = dbcon.NewQuery("INSERT INTO poll_text_replies (time, poll_id, player_id, text) VALUES (Now(), [poll_id], [client.id], '[reply_text]')") + var/datum/db_query/insert_query = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("poll_text_replies")] (time, poll_id, player_id, text) VALUES (Now(), :poll_id, :player_id, :text)", + list( + "poll_id" = poll_id, + "player_id" = client.id, + "text" = reply_text + ) + ) + if(!insert_query.Execute()) log_world("Failed to insert text vote reply for [poll_id] from user [client.id]. Error message: [insert_query.ErrorMsg()].") return diff --git a/code/modules/modular_computers/file_system/programs/generic/library.dm b/code/modules/modular_computers/file_system/programs/generic/library.dm index 73c0c7b7c67..a8a180076ff 100644 --- a/code/modules/modular_computers/file_system/programs/generic/library.dm +++ b/code/modules/modular_computers/file_system/programs/generic/library.dm @@ -26,7 +26,7 @@ The answer was five and a half years -ZeroBits var/obj/machinery/libraryscanner/scanner var/sort_by = "id" -/datum/nano_module/library/nano_ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = NANOUI_FOCUS, var/datum/nano_topic_state/state = GLOB.default_state) +/datum/nano_module/library/nano_ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = NANOUI_FOCUS, datum/nano_topic_state/state = GLOB.default_state) var/list/data = host.initial_data() if(error_message) @@ -35,11 +35,10 @@ The answer was five and a half years -ZeroBits data["current_book"] = current_book else var/list/all_entries[0] - establish_db_connection() - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) error_message = "Unable to contact External Archive. Please contact your system administrator for assistance." else - var/DBQuery/query = dbcon.NewQuery("SELECT id, author, title, category FROM library ORDER BY "+sanitizeSQL(sort_by)) + var/datum/db_query/query = SSdbcore.NewQuery("SELECT id, author, title, category FROM [format_table_name("library")] ORDER BY :sort_by", list("sort_by" = sort_by)) query.Execute() while(query.NextRow()) @@ -66,7 +65,7 @@ The answer was five and a half years -ZeroBits view_book(href_list["viewbook"]) return 1 if(href_list["viewid"]) - view_book(sanitizeSQL(input("Enter USBN:") as num|null)) + view_book(input("Enter USBN:") as num|null) return 1 if(href_list["closebook"]) current_book = null @@ -108,18 +107,20 @@ The answer was five and a half years -ZeroBits var/choice = input(usr, "Upload [B.name] by [B.author] to the External Archive?") in list("Yes", "No") if(choice == "Yes") - establish_db_connection() - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) error_message = "Network Error: Connection to the Archive has been severed." return 1 var/upload_category = input(usr, "Upload to which category?") in list("Fiction", "Non-Fiction", "Reference", "Religion") - var/sqltitle = sanitizeSQL(B.name) - var/sqlauthor = sanitizeSQL(B.author) - var/sqlcontent = sanitizeSQL(B.dat) - var/sqlcategory = sanitizeSQL(upload_category) - var/DBQuery/query = dbcon.NewQuery("INSERT INTO library (author, title, content, category) VALUES ('[sqlauthor]', '[sqltitle]', '[sqlcontent]', '[sqlcategory]')") + var/sqltitle = B.name + var/sqlauthor = B.author + var/sqlcontent = B.dat + var/sqlcategory = upload_category + var/datum/db_query/query = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("library")] (author, title, content, category) VALUES (:sqlauthor, :sqltitle, :sqlcontent, :sqlcategory)", + list("sqlauthor" = sqlauthor, "sqltitle" = sqltitle, "sqlcontent" = sqlcontent, "sqlcategory" = sqlcategory) + ) if(!query.Execute()) to_chat(usr, query.ErrorMsg()) error_message = "Network Error: Unable to upload to the Archive. Contact your system Administrator for assistance." @@ -167,17 +168,15 @@ The answer was five and a half years -ZeroBits error_message = "" return 1 -/datum/nano_module/library/proc/view_book(var/id) +/datum/nano_module/library/proc/view_book(id) if(current_book || !id) return 0 - var/sqlid = sanitizeSQL(id) - establish_db_connection() - if(!dbcon.IsConnected()) + if(!SSdbcore.Connect()) error_message = "Network Error: Connection to the Archive has been severed." return 1 - var/DBQuery/query = dbcon.NewQuery("SELECT * FROM library WHERE id=[sqlid]") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT * FROM [format_table_name("library")] WHERE id = :sqlid", list("sqlid" = id)) query.Execute() while(query.NextRow()) diff --git a/config/example/dbconfig.txt b/config/example/dbconfig.txt index 84f3d26cec5..582bc7e8793 100644 --- a/config/example/dbconfig.txt +++ b/config/example/dbconfig.txt @@ -15,3 +15,29 @@ LOGIN mylogin # Password used to access the database PASSWORD mypassword + +## Prefix to be added to the name of every table, older databases will require this be set to erro_ +## Note, this does not change the table names in the database, you will have to do that yourself. +##IE: +## TABLEPREFIX +## TABLEPREFIX SS13_ +## Remove "SS13_" if you are using the standard schema file. +TABLEPREFIX SS13_ + +## Time in seconds for asynchronous queries to timeout +## Set to 0 for infinite +ASYNC_QUERY_TIMEOUT 10 + +## Time in seconds for blocking queries to execute before slow query timeout +## Set to 0 for infinite +## Must be less than or equal to ASYNC_QUERY_TIMEOUT +BLOCKING_QUERY_TIMEOUT 5 + +## The minimum number of sql connections to keep around in the pool. Setting this higher on servers geographically away from the database can improve performance. +POOLING_MIN_SQL_CONNECTIONS 1 + +## The maximum number of sql connections to the database. +POOLING_MAX_SQL_CONNECTIONS 25 + +## The maximum number of concurrent asynchronous queries that can be pending a result before we start queuing further database queries. +MAX_CONCURRENT_QUERIES 25 From 76a31708d8a6ce6e676e92d38e0b2ad78947fcde Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 25 Oct 2025 09:33:56 -0700 Subject: [PATCH 34/44] newfile test --- code/__DEFINES/database.dm | 6 + code/controllers/subsystems/database.dm | 559 ++++++++++++++++++++++++ 2 files changed, 565 insertions(+) create mode 100644 code/__DEFINES/database.dm create mode 100644 code/controllers/subsystems/database.dm diff --git a/code/__DEFINES/database.dm b/code/__DEFINES/database.dm new file mode 100644 index 00000000000..3d20b3b9a3c --- /dev/null +++ b/code/__DEFINES/database.dm @@ -0,0 +1,6 @@ +/// When a query has been queued up for execution/is being executed +#define DB_QUERY_STARTED 0 +/// When a query is finished executing +#define DB_QUERY_FINISHED 1 +/// When there was a problem with the execution of a query. +#define DB_QUERY_BROKEN 2 diff --git a/code/controllers/subsystems/database.dm b/code/controllers/subsystems/database.dm new file mode 100644 index 00000000000..7095f9028f1 --- /dev/null +++ b/code/controllers/subsystems/database.dm @@ -0,0 +1,559 @@ +#define SHUTDOWN_QUERY_TIMELIMIT (1 MINUTES) +SUBSYSTEM_DEF(dbcore) + name = "Database" + flags = SS_TICKER + wait = 10 // Not seconds because we're running on SS_TICKER + runlevels = RUNLEVEL_INIT|RUNLEVEL_LOBBY|RUNLEVELS_DEFAULT + init_order = INIT_ORDER_DBCORE + priority = FIRE_PRIORITY_DATABASE + + var/schema_mismatch = 0 + var/db_version = 0 + /// Number of failed connection attempts this try. Resets after the timeout or successful connection + var/failed_connections = 0 + /// Max number of consecutive failures before a timeout (here and not a define so it can be vv'ed mid round if needed) + var/max_connection_failures = 5 + /// world.time that connection attempts can resume + var/failed_connection_timeout = 0 + /// Total number of times connections have had to be timed out. + var/failed_connection_timeout_count = 0 + + var/last_error + + var/max_concurrent_queries = 25 + + /// Number of all queries, reset to 0 when logged in SStime_track. Used by SStime_track + var/all_queries_num = 0 + /// Number of active queries, reset to 0 when logged in SStime_track. Used by SStime_track + var/queries_active_num = 0 + /// Number of standby queries, reset to 0 when logged in SStime_track. Used by SStime_track + var/queries_standby_num = 0 + + /// All the current queries that exist. + var/list/all_queries = list() + /// Queries being checked for timeouts. + var/list/processing_queries + + /// Queries currently being handled by database driver + var/list/datum/db_query/queries_active = list() + /// Queries pending execution, mapped to complete arguments + var/list/datum/db_query/queries_standby = list() + + /// We are in the process of shutting down and should not allow more DB connections + var/shutting_down = FALSE + + + var/connection // Arbitrary handle returned from rust_g. + + var/db_daemon_started = FALSE + +/datum/controller/subsystem/dbcore/Initialize() + //We send warnings to the admins during subsystem init, as the clients will be New'd and messages + //will queue properly with goonchat + switch(schema_mismatch) + if(1) + message_admins("Database schema ([db_version]) doesn't match the latest schema version ([DB_SCHEMA_VERSION]), this may lead to undefined behaviour or errors") + if(2) + message_admins("Could not get schema version from database") + + return ..() + +/datum/controller/subsystem/dbcore/OnConfigLoad() + . = ..() + + var/min_sql_connections = sql_pooling_min_sql_connections + var/max_sql_connections = sql_pooling_max_sql_connections + + if (max_sql_connections < min_sql_connections) + // Since we no longer have "modified" flags, just handle logically equivalent cases + log_misc("ERROR: POOLING_MAX_SQL_CONNECTIONS ([max_sql_connections]) is set lower than POOLING_MIN_SQL_CONNECTIONS ([min_sql_connections]), the values will be swapped.") + + // Swap values + var/tmp = min_sql_connections + sql_pooling_min_sql_connections = max_sql_connections + sql_pooling_max_sql_connections = tmp + + log_misc("ERROR: POOLING_MAX_SQL_CONNECTIONS ([sql_pooling_max_sql_connections]) is set lower than POOLING_MIN_SQL_CONNECTIONS ([sql_pooling_min_sql_connections]). Please check your config or the code defaults for sanity") + +/datum/controller/subsystem/dbcore/stat_entry(msg) + msg = "P:[length(all_queries)]|Active:[length(queries_active)]|Standby:[length(queries_standby)]" + return ..() + +/// Resets the tracking numbers on the subsystem. Used by SStime_track. +/datum/controller/subsystem/dbcore/proc/reset_tracking() + all_queries_num = 0 + queries_active_num = 0 + queries_standby_num = 0 + +/datum/controller/subsystem/dbcore/fire(resumed = FALSE) + if(!IsConnected()) + return + + if(!resumed) + if(!length(queries_active) && !length(queries_standby) && !length(all_queries)) + processing_queries = null + return + processing_queries = all_queries.Copy() + + // First handle the already running queries + for (var/datum/db_query/query in queries_active) + if(!process_query(query)) + queries_active -= query + + // Now lets pull in standby queries if we have room. + if (length(queries_standby) > 0 && length(queries_active) < max_concurrent_queries) + var/list/queries_to_activate = queries_standby.Copy(1, min(length(queries_standby), max_concurrent_queries) + 1) + + for (var/datum/db_query/query in queries_to_activate) + queries_standby.Remove(query) + create_active_query(query) + + // And finally, let check queries for undeleted queries, check ticking if there is a lot of work to do. + while(length(processing_queries)) + var/datum/db_query/query = popleft(processing_queries) + if(world.time - query.last_activity_time > (5 MINUTES)) + stack_trace("Found undeleted query, check the sql.log for the undeleted query and add a delete call to the query datum.") + log_misc("Undeleted query: \"[query.sql]\" LA: [query.last_activity] LAT: [query.last_activity_time]") + qdel(query) + if(MC_TICK_CHECK) + return + + +/// Helper proc for handling activating queued queries +/datum/controller/subsystem/dbcore/proc/create_active_query(datum/db_query/query) + PRIVATE_PROC(TRUE) + SHOULD_NOT_SLEEP(TRUE) + // if(IsAdminAdvancedProcCall()) + // return FALSE + run_query(query) + queries_active_num++ + queries_active += query + return query + +/datum/controller/subsystem/dbcore/proc/process_query(datum/db_query/query) + PRIVATE_PROC(TRUE) + SHOULD_NOT_SLEEP(TRUE) + // if(IsAdminAdvancedProcCall()) + // return FALSE + if(QDELETED(query)) + return FALSE + if(query.Process((TICKS2DS(wait)) / 10)) + queries_active -= query + return FALSE + return TRUE + +/datum/controller/subsystem/dbcore/proc/run_query_sync(datum/db_query/query) + // if(IsAdminAdvancedProcCall()) + // return + run_query(query) + UNTIL(query.Process()) + return query + +/datum/controller/subsystem/dbcore/proc/run_query(datum/db_query/query) + // if(IsAdminAdvancedProcCall()) + // return + query.job_id = rustg_sql_query_async(connection, query.sql, json_encode(query.arguments)) + +/datum/controller/subsystem/dbcore/proc/queue_query(datum/db_query/query) + // if(IsAdminAdvancedProcCall()) + // return + + if (!length(queries_standby) && length(queries_active) < max_concurrent_queries) + create_active_query(query) + return + + queries_standby_num++ + queries_standby |= query + +/datum/controller/subsystem/dbcore/Recover() + connection = SSdbcore.connection + +/datum/controller/subsystem/dbcore/Shutdown() + shutting_down = TRUE + var/msg = "Clearing DB queries standby:[length(queries_standby)] active: [length(queries_active)] all: [length(all_queries)]" + to_chat(world, span_boldannounce(msg)) + log_world(msg) + //This is as close as we can get to the true round end before Disconnect() without changing where it's called, defeating the reason this is a subsystem + var/endtime = REALTIMEOFDAY + SHUTDOWN_QUERY_TIMELIMIT + if(SSdbcore.Connect()) + //Take over control of all active queries + var/queries_to_check = queries_active.Copy() + queries_active.Cut() + + //Start all waiting queries + for(var/datum/db_query/query in queries_standby) + run_query(query) + queries_to_check += query + queries_standby -= query + + //wait for them all to finish + for(var/datum/db_query/query in queries_to_check) + UNTIL(query.Process() || REALTIMEOFDAY > endtime) + + msg = "Done clearing DB queries standby:[length(queries_standby)] active: [length(queries_active)] all: [length(all_queries)]" + to_chat(world, span_boldannounce(msg)) + log_world(msg) + if(IsConnected()) + Disconnect() + +/datum/controller/subsystem/dbcore/proc/Connect() + if(IsConnected()) + return TRUE + + if(connection) + Disconnect() //clear the current connection handle so isconnected() calls stop invoking rustg + connection = null //make sure its cleared even if runtimes happened + + if(failed_connection_timeout <= world.time) //it's been long enough since we failed to connect, reset the counter + failed_connections = 0 + failed_connection_timeout = 0 + + if(failed_connection_timeout > 0) + return FALSE + + if(!sqladdress) + return FALSE + + var/user = sqllogin + var/pass = sqlpass + var/db = sqldb + var/address = sqladdress + var/port = sqlport + var/timeout = max(sql_async_query_timeout, sql_blocking_query_timeout) + var/min_sql_connections = sql_pooling_min_sql_connections + var/max_sql_connections = sql_pooling_max_sql_connections + + var/result = json_decode(rustg_sql_connect_pool(json_encode(list( + "host" = address, + "port" = port, + "user" = user, + "pass" = pass, + "db_name" = db, + "read_timeout" = timeout, + "write_timeout" = timeout, + "min_threads" = min_sql_connections, + "max_threads" = max_sql_connections, + )))) + . = (result["status"] == "ok") + if (.) + connection = result["handle"] + else + connection = null + last_error = result["data"] + log_misc("Connect() failed | [last_error]") + ++failed_connections + //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for a time. + if(failed_connections > max_connection_failures) + failed_connection_timeout_count++ + //basic exponential backoff algorithm + failed_connection_timeout = world.time + ((2 ** failed_connection_timeout_count) SECONDS) + +/datum/controller/subsystem/dbcore/proc/CheckSchemaVersion() + if(sqladdress) + if(Connect()) + log_world("Database connection established.") + var/datum/db_query/query_db_version = NewQuery("SELECT version FROM [format_table_name("schema_migrations")] ORDER BY version DESC LIMIT 1") + query_db_version.Execute() + if(query_db_version.NextRow()) + var/db_version = query_db_version.item[1] + if(db_version != DB_SCHEMA_VERSION) + schema_mismatch = 1 // flag admin message about mismatch + log_misc("Database schema ([db_version]) doesn't match the expected schema version ([DB_SCHEMA_VERSION]), this may lead to undefined behaviour or errors") + else + schema_mismatch = 2 // flag admin message about missing schema version + log_misc("Could not get schema version from database") + qdel(query_db_version) + else + log_misc("Your server failed to establish a connection with the database.") + else + log_misc("Database is not enabled in configuration.") + +/datum/controller/subsystem/dbcore/proc/Disconnect() + failed_connections = 0 + if (connection) + rustg_sql_disconnect_pool(connection) + connection = null + +/datum/controller/subsystem/dbcore/proc/IsConnected() + if (!sqladdress) + return FALSE + if (!connection) + return FALSE + return json_decode(rustg_sql_connected(connection))["status"] == "online" + +/datum/controller/subsystem/dbcore/proc/ErrorMsg() + if(!sqladdress) + return "Database disabled by configuration" + return last_error + +/datum/controller/subsystem/dbcore/proc/ReportError(error) + last_error = error + +/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query, arguments, allow_during_shutdown=FALSE) + //If the subsystem is shutting down, disallow new queries + if(!allow_during_shutdown && shutting_down) + CRASH("Attempting to create a new db query during the world shutdown") + + // if(IsAdminAdvancedProcCall()) + // log_admin_private("ERROR: Advanced admin proc call led to sql query: [sql_query]. Query has been blocked") + // message_admins("ERROR: Advanced admin proc call led to sql query. Query has been blocked") + // return FALSE + return new /datum/db_query(connection, sql_query, arguments) + +/** + * Creates and executes a query without waiting for or tracking the results. + * Query is executed asynchronously (without blocking) and deleted afterwards - any results or errors are discarded. + * + * Arguments: + * * sql_query - The SQL query string to execute + * * arguments - List of arguments to pass to the query for parameter binding + * * allow_during_shutdown - If TRUE, allows query to be created during subsystem shutdown. Generally, only cleanup queries should set this. + */ +/datum/controller/subsystem/dbcore/proc/FireAndForget(sql_query, arguments, allow_during_shutdown = FALSE) + var/datum/db_query/query = NewQuery(sql_query, arguments, allow_during_shutdown) + if(!query) + return + spawn(-1) + query.Execute() + qdel(query) + +/** QuerySelect + Run a list of query datums in parallel, blocking until they all complete. + * queries - List of queries or single query datum to run. + * warn - Controls rather warn_execute() or Execute() is called. + * qdel - If you don't care about the result or checking for errors, you can have the queries be deleted afterwards. + This can be combined with invoke_async as a way of running queries async without having to care about waiting for them to finish so they can be deleted, + however you should probably just use FireAndForget instead if it's just a single query. +*/ +/datum/controller/subsystem/dbcore/proc/QuerySelect(list/queries, warn = FALSE, qdel = FALSE) + if (!islist(queries)) + if (!istype(queries, /datum/db_query)) + CRASH("Invalid query passed to QuerySelect: [queries]") + queries = list(queries) + else + queries = queries.Copy() //we don't want to hide bugs in the parent caller by removing invalid values from this list. + + for (var/datum/db_query/query as anything in queries) + if (!istype(query)) + queries -= query + stack_trace("Invalid query passed to QuerySelect: `[query]` [REF(query)]") + continue + + if (warn) + INVOKE_ASYNC(query, TYPE_PROC_REF(/datum/db_query, warn_execute)) + else + INVOKE_ASYNC(query, TYPE_PROC_REF(/datum/db_query, Execute)) + + for (var/datum/db_query/query as anything in queries) + query.sync() + if (qdel) + qdel(query) + +/* +Takes a list of rows (each row being an associated list of column => value) and inserts them via a single mass query. +Rows missing columns present in other rows will resolve to SQL NULL +You are expected to do your own escaping of the data, and expected to provide your own quotes for strings. +The duplicate_key arg can be true to automatically generate this part of the query + or set to a string that is appended to the end of the query +Ignore_errors instructes mysql to continue inserting rows if some of them have errors. + the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored +*/ +/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, warn = FALSE, async = TRUE, special_columns = null) + if (!table || !rows || !istype(rows)) + return + + // Prepare column list + var/list/columns = list() + var/list/has_question_mark = list() + for (var/list/row in rows) + for (var/column in row) + columns[column] = "?" + has_question_mark[column] = TRUE + for (var/column in special_columns) + columns[column] = special_columns[column] + has_question_mark[column] = findtext(special_columns[column], "?") + + // Prepare SQL query full of placeholders + var/list/query_parts = list("INSERT") + if (ignore_errors) + query_parts += " IGNORE" + query_parts += " INTO " + query_parts += table + query_parts += "\n([columns.Join(", ")])\nVALUES" + + var/list/arguments = list() + var/has_row = FALSE + for (var/list/row in rows) + if (has_row) + query_parts += "," + query_parts += "\n (" + var/has_col = FALSE + for (var/column in columns) + if (has_col) + query_parts += ", " + if (has_question_mark[column]) + var/name = "p[arguments.len]" + query_parts += replacetext(columns[column], "?", ":[name]") + arguments[name] = row[column] + else + query_parts += columns[column] + has_col = TRUE + query_parts += ")" + has_row = TRUE + + if (duplicate_key == TRUE) + var/list/column_list = list() + for (var/column in columns) + column_list += "[column] = VALUES([column])" + query_parts += "\nON DUPLICATE KEY UPDATE [column_list.Join(", ")]" + else if (duplicate_key != FALSE) + query_parts += duplicate_key + + var/datum/db_query/Query = NewQuery(query_parts.Join(), arguments) + if (warn) + . = Query.warn_execute(async) + else + . = Query.Execute(async) + qdel(Query) + +/datum/db_query + // Inputs + var/connection + var/sql + var/arguments + + var/datum/callback/success_callback + var/datum/callback/fail_callback + + // Status information + /// Current status of the query. + var/status + /// Job ID of the query passed by rustg. + var/job_id + var/last_error + var/last_activity + var/last_activity_time + + // Output + var/list/list/rows + var/next_row_to_take = 1 + var/affected + var/last_insert_id + + var/list/item //list of data values populated by NextRow() + +/datum/db_query/New(connection, sql, arguments) + SSdbcore.all_queries += src + SSdbcore.all_queries_num++ + Activity("Created") + item = list() + + src.connection = connection + src.sql = sql + src.arguments = arguments + +/datum/db_query/Destroy() + Close() + SSdbcore.all_queries -= src + SSdbcore.queries_standby -= src + SSdbcore.queries_active -= src + return ..() + +/datum/db_query/proc/Activity(activity) + last_activity = activity + last_activity_time = world.time + +/datum/db_query/proc/warn_execute(async = TRUE) + . = Execute(async) + if(!.) + + to_chat(usr, span_danger("A SQL error occurred during this operation, check the server logs.")) + +/datum/db_query/proc/Execute(async = TRUE, log_error = TRUE) + Activity("Execute") + if(status == DB_QUERY_STARTED) + CRASH("Attempted to start a new query while waiting on the old one") + + if(!SSdbcore.IsConnected()) + last_error = "No connection!" + return FALSE + + var/start_time + if(!async) + start_time = REALTIMEOFDAY + Close() + status = DB_QUERY_STARTED + if(async) + if(!MC_RUNNING(SSdbcore.init_stage)) + SSdbcore.run_query_sync(src) + else + SSdbcore.queue_query(src) + sync() + else + var/job_result_str = rustg_sql_query_blocking(connection, sql, json_encode(arguments)) + store_data(json_decode(job_result_str)) + + . = (status != DB_QUERY_BROKEN) + var/timed_out = !. && findtext(last_error, "Operation timed out") + if(!. && log_error) + log_misc("SQL query failed: [sql] | args=[json_encode(arguments)] | error=[last_error]") + + if(!async && timed_out) + log_misc("Slow query timeout: [sql] | start=[start_time] | end=[REALTIMEOFDAY]") + slow_query_check() + +/// Sleeps until execution of the query has finished. +/datum/db_query/proc/sync() + while(status < DB_QUERY_FINISHED) + stoplag() + +/datum/db_query/Process(seconds_per_tick) + if(status >= DB_QUERY_FINISHED) + return TRUE // we are done processing after all + + status = DB_QUERY_STARTED + var/job_result = rustg_sql_check_query(job_id) + if(job_result == RUSTG_JOB_NO_RESULTS_YET) + return FALSE //no results yet + + store_data(json_decode(job_result)) + return TRUE + +/datum/db_query/proc/store_data(result) + switch(result["status"]) + if("ok") + rows = result["rows"] + affected = result["affected"] + last_insert_id = result["last_insert_id"] + status = DB_QUERY_FINISHED + return + if("err") + last_error = result["data"] + status = DB_QUERY_BROKEN + return + if("offline") + last_error = "CONNECTION OFFLINE" + status = DB_QUERY_BROKEN + return + + +/datum/db_query/proc/slow_query_check() + message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]") + +/datum/db_query/proc/NextRow(async = TRUE) + Activity("NextRow") + + if (rows && next_row_to_take <= rows.len) + item = rows[next_row_to_take] + next_row_to_take++ + return !!item + else + return FALSE + +/datum/db_query/proc/ErrorMsg() + return last_error + +/datum/db_query/proc/Close() + rows = null + item = null +#undef SHUTDOWN_QUERY_TIMELIMIT From 14f216bc87f7264024f69ce8037f0b2855599c29 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 25 Oct 2025 09:34:48 -0700 Subject: [PATCH 35/44] newfile test --- code/__DEFINES/database.dm | 6 + code/controllers/subsystems/database.dm | 559 ++++++++++++++++++++++++ 2 files changed, 565 insertions(+) create mode 100644 code/__DEFINES/database.dm create mode 100644 code/controllers/subsystems/database.dm diff --git a/code/__DEFINES/database.dm b/code/__DEFINES/database.dm new file mode 100644 index 00000000000..3d20b3b9a3c --- /dev/null +++ b/code/__DEFINES/database.dm @@ -0,0 +1,6 @@ +/// When a query has been queued up for execution/is being executed +#define DB_QUERY_STARTED 0 +/// When a query is finished executing +#define DB_QUERY_FINISHED 1 +/// When there was a problem with the execution of a query. +#define DB_QUERY_BROKEN 2 diff --git a/code/controllers/subsystems/database.dm b/code/controllers/subsystems/database.dm new file mode 100644 index 00000000000..7095f9028f1 --- /dev/null +++ b/code/controllers/subsystems/database.dm @@ -0,0 +1,559 @@ +#define SHUTDOWN_QUERY_TIMELIMIT (1 MINUTES) +SUBSYSTEM_DEF(dbcore) + name = "Database" + flags = SS_TICKER + wait = 10 // Not seconds because we're running on SS_TICKER + runlevels = RUNLEVEL_INIT|RUNLEVEL_LOBBY|RUNLEVELS_DEFAULT + init_order = INIT_ORDER_DBCORE + priority = FIRE_PRIORITY_DATABASE + + var/schema_mismatch = 0 + var/db_version = 0 + /// Number of failed connection attempts this try. Resets after the timeout or successful connection + var/failed_connections = 0 + /// Max number of consecutive failures before a timeout (here and not a define so it can be vv'ed mid round if needed) + var/max_connection_failures = 5 + /// world.time that connection attempts can resume + var/failed_connection_timeout = 0 + /// Total number of times connections have had to be timed out. + var/failed_connection_timeout_count = 0 + + var/last_error + + var/max_concurrent_queries = 25 + + /// Number of all queries, reset to 0 when logged in SStime_track. Used by SStime_track + var/all_queries_num = 0 + /// Number of active queries, reset to 0 when logged in SStime_track. Used by SStime_track + var/queries_active_num = 0 + /// Number of standby queries, reset to 0 when logged in SStime_track. Used by SStime_track + var/queries_standby_num = 0 + + /// All the current queries that exist. + var/list/all_queries = list() + /// Queries being checked for timeouts. + var/list/processing_queries + + /// Queries currently being handled by database driver + var/list/datum/db_query/queries_active = list() + /// Queries pending execution, mapped to complete arguments + var/list/datum/db_query/queries_standby = list() + + /// We are in the process of shutting down and should not allow more DB connections + var/shutting_down = FALSE + + + var/connection // Arbitrary handle returned from rust_g. + + var/db_daemon_started = FALSE + +/datum/controller/subsystem/dbcore/Initialize() + //We send warnings to the admins during subsystem init, as the clients will be New'd and messages + //will queue properly with goonchat + switch(schema_mismatch) + if(1) + message_admins("Database schema ([db_version]) doesn't match the latest schema version ([DB_SCHEMA_VERSION]), this may lead to undefined behaviour or errors") + if(2) + message_admins("Could not get schema version from database") + + return ..() + +/datum/controller/subsystem/dbcore/OnConfigLoad() + . = ..() + + var/min_sql_connections = sql_pooling_min_sql_connections + var/max_sql_connections = sql_pooling_max_sql_connections + + if (max_sql_connections < min_sql_connections) + // Since we no longer have "modified" flags, just handle logically equivalent cases + log_misc("ERROR: POOLING_MAX_SQL_CONNECTIONS ([max_sql_connections]) is set lower than POOLING_MIN_SQL_CONNECTIONS ([min_sql_connections]), the values will be swapped.") + + // Swap values + var/tmp = min_sql_connections + sql_pooling_min_sql_connections = max_sql_connections + sql_pooling_max_sql_connections = tmp + + log_misc("ERROR: POOLING_MAX_SQL_CONNECTIONS ([sql_pooling_max_sql_connections]) is set lower than POOLING_MIN_SQL_CONNECTIONS ([sql_pooling_min_sql_connections]). Please check your config or the code defaults for sanity") + +/datum/controller/subsystem/dbcore/stat_entry(msg) + msg = "P:[length(all_queries)]|Active:[length(queries_active)]|Standby:[length(queries_standby)]" + return ..() + +/// Resets the tracking numbers on the subsystem. Used by SStime_track. +/datum/controller/subsystem/dbcore/proc/reset_tracking() + all_queries_num = 0 + queries_active_num = 0 + queries_standby_num = 0 + +/datum/controller/subsystem/dbcore/fire(resumed = FALSE) + if(!IsConnected()) + return + + if(!resumed) + if(!length(queries_active) && !length(queries_standby) && !length(all_queries)) + processing_queries = null + return + processing_queries = all_queries.Copy() + + // First handle the already running queries + for (var/datum/db_query/query in queries_active) + if(!process_query(query)) + queries_active -= query + + // Now lets pull in standby queries if we have room. + if (length(queries_standby) > 0 && length(queries_active) < max_concurrent_queries) + var/list/queries_to_activate = queries_standby.Copy(1, min(length(queries_standby), max_concurrent_queries) + 1) + + for (var/datum/db_query/query in queries_to_activate) + queries_standby.Remove(query) + create_active_query(query) + + // And finally, let check queries for undeleted queries, check ticking if there is a lot of work to do. + while(length(processing_queries)) + var/datum/db_query/query = popleft(processing_queries) + if(world.time - query.last_activity_time > (5 MINUTES)) + stack_trace("Found undeleted query, check the sql.log for the undeleted query and add a delete call to the query datum.") + log_misc("Undeleted query: \"[query.sql]\" LA: [query.last_activity] LAT: [query.last_activity_time]") + qdel(query) + if(MC_TICK_CHECK) + return + + +/// Helper proc for handling activating queued queries +/datum/controller/subsystem/dbcore/proc/create_active_query(datum/db_query/query) + PRIVATE_PROC(TRUE) + SHOULD_NOT_SLEEP(TRUE) + // if(IsAdminAdvancedProcCall()) + // return FALSE + run_query(query) + queries_active_num++ + queries_active += query + return query + +/datum/controller/subsystem/dbcore/proc/process_query(datum/db_query/query) + PRIVATE_PROC(TRUE) + SHOULD_NOT_SLEEP(TRUE) + // if(IsAdminAdvancedProcCall()) + // return FALSE + if(QDELETED(query)) + return FALSE + if(query.Process((TICKS2DS(wait)) / 10)) + queries_active -= query + return FALSE + return TRUE + +/datum/controller/subsystem/dbcore/proc/run_query_sync(datum/db_query/query) + // if(IsAdminAdvancedProcCall()) + // return + run_query(query) + UNTIL(query.Process()) + return query + +/datum/controller/subsystem/dbcore/proc/run_query(datum/db_query/query) + // if(IsAdminAdvancedProcCall()) + // return + query.job_id = rustg_sql_query_async(connection, query.sql, json_encode(query.arguments)) + +/datum/controller/subsystem/dbcore/proc/queue_query(datum/db_query/query) + // if(IsAdminAdvancedProcCall()) + // return + + if (!length(queries_standby) && length(queries_active) < max_concurrent_queries) + create_active_query(query) + return + + queries_standby_num++ + queries_standby |= query + +/datum/controller/subsystem/dbcore/Recover() + connection = SSdbcore.connection + +/datum/controller/subsystem/dbcore/Shutdown() + shutting_down = TRUE + var/msg = "Clearing DB queries standby:[length(queries_standby)] active: [length(queries_active)] all: [length(all_queries)]" + to_chat(world, span_boldannounce(msg)) + log_world(msg) + //This is as close as we can get to the true round end before Disconnect() without changing where it's called, defeating the reason this is a subsystem + var/endtime = REALTIMEOFDAY + SHUTDOWN_QUERY_TIMELIMIT + if(SSdbcore.Connect()) + //Take over control of all active queries + var/queries_to_check = queries_active.Copy() + queries_active.Cut() + + //Start all waiting queries + for(var/datum/db_query/query in queries_standby) + run_query(query) + queries_to_check += query + queries_standby -= query + + //wait for them all to finish + for(var/datum/db_query/query in queries_to_check) + UNTIL(query.Process() || REALTIMEOFDAY > endtime) + + msg = "Done clearing DB queries standby:[length(queries_standby)] active: [length(queries_active)] all: [length(all_queries)]" + to_chat(world, span_boldannounce(msg)) + log_world(msg) + if(IsConnected()) + Disconnect() + +/datum/controller/subsystem/dbcore/proc/Connect() + if(IsConnected()) + return TRUE + + if(connection) + Disconnect() //clear the current connection handle so isconnected() calls stop invoking rustg + connection = null //make sure its cleared even if runtimes happened + + if(failed_connection_timeout <= world.time) //it's been long enough since we failed to connect, reset the counter + failed_connections = 0 + failed_connection_timeout = 0 + + if(failed_connection_timeout > 0) + return FALSE + + if(!sqladdress) + return FALSE + + var/user = sqllogin + var/pass = sqlpass + var/db = sqldb + var/address = sqladdress + var/port = sqlport + var/timeout = max(sql_async_query_timeout, sql_blocking_query_timeout) + var/min_sql_connections = sql_pooling_min_sql_connections + var/max_sql_connections = sql_pooling_max_sql_connections + + var/result = json_decode(rustg_sql_connect_pool(json_encode(list( + "host" = address, + "port" = port, + "user" = user, + "pass" = pass, + "db_name" = db, + "read_timeout" = timeout, + "write_timeout" = timeout, + "min_threads" = min_sql_connections, + "max_threads" = max_sql_connections, + )))) + . = (result["status"] == "ok") + if (.) + connection = result["handle"] + else + connection = null + last_error = result["data"] + log_misc("Connect() failed | [last_error]") + ++failed_connections + //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for a time. + if(failed_connections > max_connection_failures) + failed_connection_timeout_count++ + //basic exponential backoff algorithm + failed_connection_timeout = world.time + ((2 ** failed_connection_timeout_count) SECONDS) + +/datum/controller/subsystem/dbcore/proc/CheckSchemaVersion() + if(sqladdress) + if(Connect()) + log_world("Database connection established.") + var/datum/db_query/query_db_version = NewQuery("SELECT version FROM [format_table_name("schema_migrations")] ORDER BY version DESC LIMIT 1") + query_db_version.Execute() + if(query_db_version.NextRow()) + var/db_version = query_db_version.item[1] + if(db_version != DB_SCHEMA_VERSION) + schema_mismatch = 1 // flag admin message about mismatch + log_misc("Database schema ([db_version]) doesn't match the expected schema version ([DB_SCHEMA_VERSION]), this may lead to undefined behaviour or errors") + else + schema_mismatch = 2 // flag admin message about missing schema version + log_misc("Could not get schema version from database") + qdel(query_db_version) + else + log_misc("Your server failed to establish a connection with the database.") + else + log_misc("Database is not enabled in configuration.") + +/datum/controller/subsystem/dbcore/proc/Disconnect() + failed_connections = 0 + if (connection) + rustg_sql_disconnect_pool(connection) + connection = null + +/datum/controller/subsystem/dbcore/proc/IsConnected() + if (!sqladdress) + return FALSE + if (!connection) + return FALSE + return json_decode(rustg_sql_connected(connection))["status"] == "online" + +/datum/controller/subsystem/dbcore/proc/ErrorMsg() + if(!sqladdress) + return "Database disabled by configuration" + return last_error + +/datum/controller/subsystem/dbcore/proc/ReportError(error) + last_error = error + +/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query, arguments, allow_during_shutdown=FALSE) + //If the subsystem is shutting down, disallow new queries + if(!allow_during_shutdown && shutting_down) + CRASH("Attempting to create a new db query during the world shutdown") + + // if(IsAdminAdvancedProcCall()) + // log_admin_private("ERROR: Advanced admin proc call led to sql query: [sql_query]. Query has been blocked") + // message_admins("ERROR: Advanced admin proc call led to sql query. Query has been blocked") + // return FALSE + return new /datum/db_query(connection, sql_query, arguments) + +/** + * Creates and executes a query without waiting for or tracking the results. + * Query is executed asynchronously (without blocking) and deleted afterwards - any results or errors are discarded. + * + * Arguments: + * * sql_query - The SQL query string to execute + * * arguments - List of arguments to pass to the query for parameter binding + * * allow_during_shutdown - If TRUE, allows query to be created during subsystem shutdown. Generally, only cleanup queries should set this. + */ +/datum/controller/subsystem/dbcore/proc/FireAndForget(sql_query, arguments, allow_during_shutdown = FALSE) + var/datum/db_query/query = NewQuery(sql_query, arguments, allow_during_shutdown) + if(!query) + return + spawn(-1) + query.Execute() + qdel(query) + +/** QuerySelect + Run a list of query datums in parallel, blocking until they all complete. + * queries - List of queries or single query datum to run. + * warn - Controls rather warn_execute() or Execute() is called. + * qdel - If you don't care about the result or checking for errors, you can have the queries be deleted afterwards. + This can be combined with invoke_async as a way of running queries async without having to care about waiting for them to finish so they can be deleted, + however you should probably just use FireAndForget instead if it's just a single query. +*/ +/datum/controller/subsystem/dbcore/proc/QuerySelect(list/queries, warn = FALSE, qdel = FALSE) + if (!islist(queries)) + if (!istype(queries, /datum/db_query)) + CRASH("Invalid query passed to QuerySelect: [queries]") + queries = list(queries) + else + queries = queries.Copy() //we don't want to hide bugs in the parent caller by removing invalid values from this list. + + for (var/datum/db_query/query as anything in queries) + if (!istype(query)) + queries -= query + stack_trace("Invalid query passed to QuerySelect: `[query]` [REF(query)]") + continue + + if (warn) + INVOKE_ASYNC(query, TYPE_PROC_REF(/datum/db_query, warn_execute)) + else + INVOKE_ASYNC(query, TYPE_PROC_REF(/datum/db_query, Execute)) + + for (var/datum/db_query/query as anything in queries) + query.sync() + if (qdel) + qdel(query) + +/* +Takes a list of rows (each row being an associated list of column => value) and inserts them via a single mass query. +Rows missing columns present in other rows will resolve to SQL NULL +You are expected to do your own escaping of the data, and expected to provide your own quotes for strings. +The duplicate_key arg can be true to automatically generate this part of the query + or set to a string that is appended to the end of the query +Ignore_errors instructes mysql to continue inserting rows if some of them have errors. + the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored +*/ +/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, warn = FALSE, async = TRUE, special_columns = null) + if (!table || !rows || !istype(rows)) + return + + // Prepare column list + var/list/columns = list() + var/list/has_question_mark = list() + for (var/list/row in rows) + for (var/column in row) + columns[column] = "?" + has_question_mark[column] = TRUE + for (var/column in special_columns) + columns[column] = special_columns[column] + has_question_mark[column] = findtext(special_columns[column], "?") + + // Prepare SQL query full of placeholders + var/list/query_parts = list("INSERT") + if (ignore_errors) + query_parts += " IGNORE" + query_parts += " INTO " + query_parts += table + query_parts += "\n([columns.Join(", ")])\nVALUES" + + var/list/arguments = list() + var/has_row = FALSE + for (var/list/row in rows) + if (has_row) + query_parts += "," + query_parts += "\n (" + var/has_col = FALSE + for (var/column in columns) + if (has_col) + query_parts += ", " + if (has_question_mark[column]) + var/name = "p[arguments.len]" + query_parts += replacetext(columns[column], "?", ":[name]") + arguments[name] = row[column] + else + query_parts += columns[column] + has_col = TRUE + query_parts += ")" + has_row = TRUE + + if (duplicate_key == TRUE) + var/list/column_list = list() + for (var/column in columns) + column_list += "[column] = VALUES([column])" + query_parts += "\nON DUPLICATE KEY UPDATE [column_list.Join(", ")]" + else if (duplicate_key != FALSE) + query_parts += duplicate_key + + var/datum/db_query/Query = NewQuery(query_parts.Join(), arguments) + if (warn) + . = Query.warn_execute(async) + else + . = Query.Execute(async) + qdel(Query) + +/datum/db_query + // Inputs + var/connection + var/sql + var/arguments + + var/datum/callback/success_callback + var/datum/callback/fail_callback + + // Status information + /// Current status of the query. + var/status + /// Job ID of the query passed by rustg. + var/job_id + var/last_error + var/last_activity + var/last_activity_time + + // Output + var/list/list/rows + var/next_row_to_take = 1 + var/affected + var/last_insert_id + + var/list/item //list of data values populated by NextRow() + +/datum/db_query/New(connection, sql, arguments) + SSdbcore.all_queries += src + SSdbcore.all_queries_num++ + Activity("Created") + item = list() + + src.connection = connection + src.sql = sql + src.arguments = arguments + +/datum/db_query/Destroy() + Close() + SSdbcore.all_queries -= src + SSdbcore.queries_standby -= src + SSdbcore.queries_active -= src + return ..() + +/datum/db_query/proc/Activity(activity) + last_activity = activity + last_activity_time = world.time + +/datum/db_query/proc/warn_execute(async = TRUE) + . = Execute(async) + if(!.) + + to_chat(usr, span_danger("A SQL error occurred during this operation, check the server logs.")) + +/datum/db_query/proc/Execute(async = TRUE, log_error = TRUE) + Activity("Execute") + if(status == DB_QUERY_STARTED) + CRASH("Attempted to start a new query while waiting on the old one") + + if(!SSdbcore.IsConnected()) + last_error = "No connection!" + return FALSE + + var/start_time + if(!async) + start_time = REALTIMEOFDAY + Close() + status = DB_QUERY_STARTED + if(async) + if(!MC_RUNNING(SSdbcore.init_stage)) + SSdbcore.run_query_sync(src) + else + SSdbcore.queue_query(src) + sync() + else + var/job_result_str = rustg_sql_query_blocking(connection, sql, json_encode(arguments)) + store_data(json_decode(job_result_str)) + + . = (status != DB_QUERY_BROKEN) + var/timed_out = !. && findtext(last_error, "Operation timed out") + if(!. && log_error) + log_misc("SQL query failed: [sql] | args=[json_encode(arguments)] | error=[last_error]") + + if(!async && timed_out) + log_misc("Slow query timeout: [sql] | start=[start_time] | end=[REALTIMEOFDAY]") + slow_query_check() + +/// Sleeps until execution of the query has finished. +/datum/db_query/proc/sync() + while(status < DB_QUERY_FINISHED) + stoplag() + +/datum/db_query/Process(seconds_per_tick) + if(status >= DB_QUERY_FINISHED) + return TRUE // we are done processing after all + + status = DB_QUERY_STARTED + var/job_result = rustg_sql_check_query(job_id) + if(job_result == RUSTG_JOB_NO_RESULTS_YET) + return FALSE //no results yet + + store_data(json_decode(job_result)) + return TRUE + +/datum/db_query/proc/store_data(result) + switch(result["status"]) + if("ok") + rows = result["rows"] + affected = result["affected"] + last_insert_id = result["last_insert_id"] + status = DB_QUERY_FINISHED + return + if("err") + last_error = result["data"] + status = DB_QUERY_BROKEN + return + if("offline") + last_error = "CONNECTION OFFLINE" + status = DB_QUERY_BROKEN + return + + +/datum/db_query/proc/slow_query_check() + message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]") + +/datum/db_query/proc/NextRow(async = TRUE) + Activity("NextRow") + + if (rows && next_row_to_take <= rows.len) + item = rows[next_row_to_take] + next_row_to_take++ + return !!item + else + return FALSE + +/datum/db_query/proc/ErrorMsg() + return last_error + +/datum/db_query/proc/Close() + rows = null + item = null +#undef SHUTDOWN_QUERY_TIMELIMIT From d58428318b1a52e348c3a77de88530b2af32b6d5 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 25 Oct 2025 09:37:40 -0700 Subject: [PATCH 36/44] newfile2 test --- .../admin/verbs/reestablish_db_connection.dm | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 code/modules/admin/verbs/reestablish_db_connection.dm diff --git a/code/modules/admin/verbs/reestablish_db_connection.dm b/code/modules/admin/verbs/reestablish_db_connection.dm new file mode 100644 index 00000000000..26751c4efb3 --- /dev/null +++ b/code/modules/admin/verbs/reestablish_db_connection.dm @@ -0,0 +1,30 @@ +/client/proc/reestablish_db_connection() + set category = "Server" + set name = "Reestablish DB Connection" + if (!sqladdress) + to_chat(usr, span_adminnotice("The Database is not enabled!"), confidential = TRUE) + return + + if (SSdbcore.IsConnected()) + if (!check_rights(R_DEBUG,0)) + alert(usr,"The database is already connected! (Only those with +debug can force a reconnection)", "The database is already connected!") + return + + var/reconnect = alert(usr,"The database is already connected! If you *KNOW* that this is incorrect, you can force a reconnection", "The database is already connected!", "Force Reconnect", "Cancel") + if (reconnect != "Force Reconnect") + return + + SSdbcore.Disconnect() + log_admin("[key_name(usr)] has forced the database to disconnect") + message_admins("[key_name_admin(usr)] has forced the database to disconnect!") + // SSblackbox.record_feedback("tally", "admin_verb", 1, "Force Reestablished Database Connection") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + log_admin("[key_name(usr)] is attempting to re-establish the DB Connection") + message_admins("[key_name_admin(usr)] is attempting to re-establish the DB Connection") + // SSblackbox.record_feedback("tally", "admin_verb", 1, "Reestablished Database Connection") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + SSdbcore.failed_connections = 0 + if(!SSdbcore.Connect()) + message_admins("Database connection failed: " + SSdbcore.ErrorMsg()) + else + message_admins("Database connection re-established") From c5893a560faf2944f2156a2f53fe811c148c4c56 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 25 Oct 2025 09:46:21 -0700 Subject: [PATCH 37/44] speedup test --- tools/ci/install_byond.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/ci/install_byond.sh b/tools/ci/install_byond.sh index 7901f750605..3ae5afe1104 100755 --- a/tools/ci/install_byond.sh +++ b/tools/ci/install_byond.sh @@ -2,6 +2,12 @@ set -euo pipefail source dependencies.sh +# This cuts some time down by omitting `man` +sudo tee /etc/dpkg/dpkg.cfg.d/01_nodoc > /dev/null << 'EOF' +path-exclude /usr/share/doc/* +path-exclude /usr/share/man/* +path-exclude /usr/share/info/* +EOF if [ -d "$HOME/BYOND/byond/bin" ] && grep -Fxq "${BYOND_MAJOR}.${BYOND_MINOR}" $HOME/BYOND/version.txt; then From 19f0d9471261a2970168637ca78ddf2764dfa8af Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 25 Oct 2025 09:49:39 -0700 Subject: [PATCH 38/44] bad comment prune Co-authored-by: Chen Marisa <18307183+flleeppyy@users.noreply.github.com> --- code/modules/admin/verbs/reestablish_db_connection.dm | 4 ---- 1 file changed, 4 deletions(-) diff --git a/code/modules/admin/verbs/reestablish_db_connection.dm b/code/modules/admin/verbs/reestablish_db_connection.dm index 26751c4efb3..0716abbfeab 100644 --- a/code/modules/admin/verbs/reestablish_db_connection.dm +++ b/code/modules/admin/verbs/reestablish_db_connection.dm @@ -17,12 +17,8 @@ SSdbcore.Disconnect() log_admin("[key_name(usr)] has forced the database to disconnect") message_admins("[key_name_admin(usr)] has forced the database to disconnect!") - // SSblackbox.record_feedback("tally", "admin_verb", 1, "Force Reestablished Database Connection") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - log_admin("[key_name(usr)] is attempting to re-establish the DB Connection") message_admins("[key_name_admin(usr)] is attempting to re-establish the DB Connection") - // SSblackbox.record_feedback("tally", "admin_verb", 1, "Reestablished Database Connection") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - SSdbcore.failed_connections = 0 if(!SSdbcore.Connect()) message_admins("Database connection failed: " + SSdbcore.ErrorMsg()) From 100cc713f79268441007b0327f8cb11a8a1bec3f Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 25 Oct 2025 10:03:20 -0700 Subject: [PATCH 39/44] mini reset --- tools/ci/ci_config.txt | 15 ++++++++++----- tools/ci/run_server.sh | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/tools/ci/ci_config.txt b/tools/ci/ci_config.txt index fc143c33b3c..96297012c77 100644 --- a/tools/ci/ci_config.txt +++ b/tools/ci/ci_config.txt @@ -1,5 +1,10 @@ -address 127.0.0.1 -port 3306 -database tg_ci -login root -password root +SQL_ENABLED +ADDRESS 127.0.0.1 +PORT 3306 +DATABASE tg_ci +FEEDBACK_DATABASE tg_ci +FEEDBACK_TABLEPREFIX +FEEDBACK_LOGIN root +FEEDBACK_PASSWORD root +LAVALAND_BUDGET 0 +SPACE_BUDGET 0 \ No newline at end of file diff --git a/tools/ci/run_server.sh b/tools/ci/run_server.sh index 259f5a6c188..44bb806ff46 100755 --- a/tools/ci/run_server.sh +++ b/tools/ci/run_server.sh @@ -5,7 +5,7 @@ tools/deploy.sh ci_test # mkdir ci_test/config #test config -cp tools/ci/ci_config.txt ci_test/config/dbconfig.txt +cp tools/ci/ci_config.txt ci_test/config/config.txt cd ci_test DreamDaemon cev_eris.dmb -close -trusted -verbose -params "log-directory=ci" From 472ce9a455cb05232e03f117cfb6cbe07c07e1cf Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 25 Oct 2025 10:07:40 -0700 Subject: [PATCH 40/44] woot woot --- tools/ci/run_server.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/ci/run_server.sh b/tools/ci/run_server.sh index 44bb806ff46..4b96b1ecb78 100755 --- a/tools/ci/run_server.sh +++ b/tools/ci/run_server.sh @@ -5,7 +5,8 @@ tools/deploy.sh ci_test # mkdir ci_test/config #test config -cp tools/ci/ci_config.txt ci_test/config/config.txt +cp config/example/* config/ +cp tools/ci/ci_dbconfig.txt config/dbconfig.txt cd ci_test DreamDaemon cev_eris.dmb -close -trusted -verbose -params "log-directory=ci" From 9c894ef5795737b692cf9bd5934fa6d0c40fb63b Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 25 Oct 2025 10:13:15 -0700 Subject: [PATCH 41/44] testfix --- tools/ci/run_server.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/ci/run_server.sh b/tools/ci/run_server.sh index 4b96b1ecb78..9a174c2d44d 100755 --- a/tools/ci/run_server.sh +++ b/tools/ci/run_server.sh @@ -5,8 +5,7 @@ tools/deploy.sh ci_test # mkdir ci_test/config #test config -cp config/example/* config/ -cp tools/ci/ci_dbconfig.txt config/dbconfig.txt +cp tools/ci/dbconfig.txt config/dbconfig.txt cd ci_test DreamDaemon cev_eris.dmb -close -trusted -verbose -params "log-directory=ci" From 6e32e68f3bd0f89adb78ab69e99227ac6a06079b Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 25 Oct 2025 10:29:15 -0700 Subject: [PATCH 42/44] update test 2 --- dependencies.sh | 8 ++++---- tools/ci/install_byond.sh | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dependencies.sh b/dependencies.sh index 6faca017b19..716e0de8eec 100644 --- a/dependencies.sh +++ b/dependencies.sh @@ -8,18 +8,18 @@ export BYOND_MAJOR=514 export BYOND_MINOR=1568 #rust_g git tag -export RUST_G_VERSION=1.0.1 +export RUST_G_VERSION=3.11.0 #node version export NODE_VERSION=14 -export NODE_VERSION_LTS=20.12.0 +export NODE_VERSION_LTS=22.11.0 export NODE_VERSION_COMPAT=14.16.1 # SpacemanDMM git tag -export SPACEMAN_DMM_VERSION=suite-1.7.1 +export SPACEMAN_DMM_VERSION=suite-1.10 # Extools git tag export EXTOOLS_VERSION=v0.0.7 # Python version for mapmerge and other tools -export PYTHON_VERSION=3.7.9 +export PYTHON_VERSION=3.11.8 diff --git a/tools/ci/install_byond.sh b/tools/ci/install_byond.sh index 3ae5afe1104..3356a2fa5ae 100755 --- a/tools/ci/install_byond.sh +++ b/tools/ci/install_byond.sh @@ -9,6 +9,10 @@ path-exclude /usr/share/man/* path-exclude /usr/share/info/* EOF +sudo dpkg --add-architecture i386 +sudo apt-get update +sudo apt install libcurl4:i386 + if [ -d "$HOME/BYOND/byond/bin" ] && grep -Fxq "${BYOND_MAJOR}.${BYOND_MINOR}" $HOME/BYOND/version.txt; then echo "Using cached directory." From 11627cef229f62d465b13398da68afe1e7634a0c Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 25 Oct 2025 10:32:54 -0700 Subject: [PATCH 43/44] good'nuff --- .github/workflows/ci_suite.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index 42171dc5f07..b8c9a4cc0a6 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -111,9 +111,6 @@ jobs: mysql -u root -proot tg_ci < schema.sql - name: Install rust-g run: | - sudo dpkg --add-architecture i386 - sudo apt update || true - sudo apt install -o APT::Immediate-Configure=false libssl3:i386 bash tools/ci/install_rust_g.sh - name: Compile Tests run: | From e8cb6362e6ddc9698d2b38144c4ef5f30dcae788 Mon Sep 17 00:00:00 2001 From: vode-code <65709050+vode-code@users.noreply.github.com> Date: Sat, 25 Oct 2025 10:37:51 -0700 Subject: [PATCH 44/44] namefix and test test --- tools/ci/ci_config.txt | 10 ---------- tools/ci/ci_dbconfig.txt | 6 ++++++ tools/ci/run_server.sh | 3 ++- 3 files changed, 8 insertions(+), 11 deletions(-) delete mode 100644 tools/ci/ci_config.txt create mode 100644 tools/ci/ci_dbconfig.txt diff --git a/tools/ci/ci_config.txt b/tools/ci/ci_config.txt deleted file mode 100644 index 96297012c77..00000000000 --- a/tools/ci/ci_config.txt +++ /dev/null @@ -1,10 +0,0 @@ -SQL_ENABLED -ADDRESS 127.0.0.1 -PORT 3306 -DATABASE tg_ci -FEEDBACK_DATABASE tg_ci -FEEDBACK_TABLEPREFIX -FEEDBACK_LOGIN root -FEEDBACK_PASSWORD root -LAVALAND_BUDGET 0 -SPACE_BUDGET 0 \ No newline at end of file diff --git a/tools/ci/ci_dbconfig.txt b/tools/ci/ci_dbconfig.txt new file mode 100644 index 00000000000..e0ffe0c4e59 --- /dev/null +++ b/tools/ci/ci_dbconfig.txt @@ -0,0 +1,6 @@ +ADDRESS 127.0.0.1 +PORT 3306 +DATABASE tg_ci +TABLEPREFIX +LOGIN root +PASSWORD root \ No newline at end of file diff --git a/tools/ci/run_server.sh b/tools/ci/run_server.sh index 9a174c2d44d..4b96b1ecb78 100755 --- a/tools/ci/run_server.sh +++ b/tools/ci/run_server.sh @@ -5,7 +5,8 @@ tools/deploy.sh ci_test # mkdir ci_test/config #test config -cp tools/ci/dbconfig.txt config/dbconfig.txt +cp config/example/* config/ +cp tools/ci/ci_dbconfig.txt config/dbconfig.txt cd ci_test DreamDaemon cev_eris.dmb -close -trusted -verbose -params "log-directory=ci"