/**
* Orthanc - A Lightweight, RESTful DICOM Store
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
* Department, University Hospital of Liege, Belgium
* Copyright (C) 2017-2023 Osimis S.A., Belgium
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program. If not, see
* .
**/
#if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
// Must be the first to be sure to use the Orthanc framework shared library
# include
#endif
#include
#include "../Sources/Toolbox.h"
#include "../Sources/SQLite/Connection.h"
#include "../Sources/SQLite/Statement.h"
#include "../Sources/SQLite/Transaction.h"
#include
using namespace Orthanc;
using namespace Orthanc::SQLite;
/********************************************************************
** Tests from
** http://src.chromium.org/viewvc/chrome/trunk/src/sql/connection_unittest.cc
********************************************************************/
namespace
{
class SQLConnectionTest : public testing::Test
{
public:
SQLConnectionTest()
{
}
virtual ~SQLConnectionTest() ORTHANC_OVERRIDE
{
}
virtual void SetUp() ORTHANC_OVERRIDE
{
db_.OpenInMemory();
}
virtual void TearDown() ORTHANC_OVERRIDE
{
db_.Close();
}
Connection& db()
{
return db_;
}
private:
Connection db_;
};
}
TEST_F(SQLConnectionTest, Execute)
{
// Valid statement should return true.
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
EXPECT_EQ(SQLITE_OK, db().GetErrorCode());
// Invalid statement should fail.
ASSERT_EQ(SQLITE_ERROR,
db().ExecuteAndReturnErrorCode("CREATE TAB foo (a, b"));
EXPECT_EQ(SQLITE_ERROR, db().GetErrorCode());
}
TEST_F(SQLConnectionTest, ExecuteWithErrorCode) {
ASSERT_EQ(SQLITE_OK,
db().ExecuteAndReturnErrorCode("CREATE TABLE foo (a, b)"));
ASSERT_EQ(SQLITE_ERROR,
db().ExecuteAndReturnErrorCode("CREATE TABLE TABLE"));
ASSERT_EQ(SQLITE_ERROR,
db().ExecuteAndReturnErrorCode(
"INSERT INTO foo(a, b) VALUES (1, 2, 3, 4)"));
}
TEST_F(SQLConnectionTest, CachedStatement) {
StatementId id1("foo", 12);
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
ASSERT_TRUE(db().Execute("INSERT INTO foo(a, b) VALUES (12, 13)"));
// Create a new cached statement.
{
Statement s(db(), id1, "SELECT a FROM foo");
ASSERT_TRUE(s.Step());
EXPECT_EQ(12, s.ColumnInt(0));
}
// The statement should be cached still.
EXPECT_TRUE(db().HasCachedStatement(id1));
{
// Get the same statement using different SQL. This should ignore our
// SQL and use the cached one (so it will be valid).
Statement s(db(), id1, "something invalid(");
ASSERT_TRUE(s.Step());
EXPECT_EQ(12, s.ColumnInt(0));
}
// Make sure other statements aren't marked as cached.
EXPECT_FALSE(db().HasCachedStatement(SQLITE_FROM_HERE));
}
TEST_F(SQLConnectionTest, IsSQLValidTest) {
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
ASSERT_TRUE(db().IsSQLValid("SELECT a FROM foo"));
ASSERT_FALSE(db().IsSQLValid("SELECT no_exist FROM foo"));
}
TEST_F(SQLConnectionTest, DoesStuffExist) {
// Test DoesTableExist.
EXPECT_FALSE(db().DoesTableExist("foo"));
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
EXPECT_TRUE(db().DoesTableExist("foo"));
// Should be case sensitive.
EXPECT_FALSE(db().DoesTableExist("FOO"));
// Test DoesColumnExist.
EXPECT_FALSE(db().DoesColumnExist("foo", "bar"));
EXPECT_TRUE(db().DoesColumnExist("foo", "a"));
// Testing for a column on a nonexistent table.
EXPECT_FALSE(db().DoesColumnExist("bar", "b"));
}
TEST_F(SQLConnectionTest, GetLastInsertRowId) {
ASSERT_TRUE(db().Execute("CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"));
ASSERT_TRUE(db().Execute("INSERT INTO foo (value) VALUES (12)"));
// Last insert row ID should be valid.
int64_t row = db().GetLastInsertRowId();
EXPECT_LT(0, row);
// It should be the primary key of the row we just inserted.
Statement s(db(), "SELECT value FROM foo WHERE id=?");
s.BindInt64(0, row);
ASSERT_TRUE(s.Step());
EXPECT_EQ(12, s.ColumnInt(0));
}
TEST_F(SQLConnectionTest, Rollback) {
ASSERT_TRUE(db().BeginTransaction());
ASSERT_TRUE(db().BeginTransaction());
EXPECT_EQ(2, db().GetTransactionNesting());
db().RollbackTransaction();
EXPECT_FALSE(db().CommitTransaction());
EXPECT_TRUE(db().BeginTransaction());
}
/********************************************************************
** Tests from
** http://src.chromium.org/viewvc/chrome/trunk/src/sql/statement_unittest.cc
********************************************************************/
namespace Orthanc
{
namespace SQLite
{
class SQLStatementTest : public SQLConnectionTest
{
};
TEST_F(SQLStatementTest, Run) {
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)"));
Statement s(db(), "SELECT b FROM foo WHERE a=?");
// Stepping it won't work since we haven't bound the value.
EXPECT_FALSE(s.Step());
// Run should fail since this produces output, and we should use Step(). This
// gets a bit wonky since sqlite says this is OK so succeeded is set.
s.Reset(true);
s.BindInt(0, 3);
EXPECT_FALSE(s.Run());
EXPECT_EQ(SQLITE_ROW, db().GetErrorCode());
// Resetting it should put it back to the previous state (not runnable).
s.Reset(true);
// Binding and stepping should produce one row.
s.BindInt(0, 3);
EXPECT_TRUE(s.Step());
EXPECT_EQ(12, s.ColumnInt(0));
EXPECT_FALSE(s.Step());
}
TEST_F(SQLStatementTest, BasicErrorCallback) {
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)"));
// Insert in the foo table the primary key. It is an error to insert
// something other than an number. This error causes the error callback
// handler to be called with SQLITE_MISMATCH as error code.
Statement s(db(), "INSERT INTO foo (a) VALUES (?)");
s.BindCString(0, "bad bad");
EXPECT_THROW(s.Run(), OrthancException);
}
TEST_F(SQLStatementTest, Reset) {
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)"));
ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (4, 13)"));
Statement s(db(), "SELECT b FROM foo WHERE a = ? ");
s.BindInt(0, 3);
ASSERT_TRUE(s.Step());
EXPECT_EQ(12, s.ColumnInt(0));
ASSERT_FALSE(s.Step());
s.Reset(false);
// Verify that we can get all rows again.
ASSERT_TRUE(s.Step());
EXPECT_EQ(12, s.ColumnInt(0));
EXPECT_FALSE(s.Step());
s.Reset(true);
ASSERT_FALSE(s.Step());
}
}
}
/********************************************************************
** Tests from
** http://src.chromium.org/viewvc/chrome/trunk/src/sql/transaction_unittest.cc
********************************************************************/
namespace
{
class SQLTransactionTest : public SQLConnectionTest
{
public:
virtual void SetUp() ORTHANC_OVERRIDE
{
SQLConnectionTest::SetUp();
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
}
// Returns the number of rows in table "foo".
int CountFoo()
{
Statement count(db(), "SELECT count(*) FROM foo");
count.Step();
return count.ColumnInt(0);
}
};
}
TEST_F(SQLTransactionTest, Commit) {
{
Transaction t(db());
EXPECT_FALSE(t.IsOpen());
t.Begin();
EXPECT_TRUE(t.IsOpen());
EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
t.Commit();
EXPECT_FALSE(t.IsOpen());
}
EXPECT_EQ(1, CountFoo());
}
TEST_F(SQLTransactionTest, Rollback) {
// Test some basic initialization, and that rollback runs when you exit the
// scope.
{
Transaction t(db());
EXPECT_FALSE(t.IsOpen());
t.Begin();
EXPECT_TRUE(t.IsOpen());
EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
}
// Nothing should have been committed since it was implicitly rolled back.
EXPECT_EQ(0, CountFoo());
// Test explicit rollback.
Transaction t2(db());
EXPECT_FALSE(t2.IsOpen());
t2.Begin();
EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
t2.Rollback();
EXPECT_FALSE(t2.IsOpen());
// Nothing should have been committed since it was explicitly rolled back.
EXPECT_EQ(0, CountFoo());
}
// Rolling back any part of a transaction should roll back all of them.
TEST_F(SQLTransactionTest, NestedRollback) {
EXPECT_EQ(0, db().GetTransactionNesting());
// Outermost transaction.
{
Transaction outer(db());
outer.Begin();
EXPECT_EQ(1, db().GetTransactionNesting());
// The first inner one gets committed.
{
Transaction inner1(db());
inner1.Begin();
EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
EXPECT_EQ(2, db().GetTransactionNesting());
inner1.Commit();
EXPECT_EQ(1, db().GetTransactionNesting());
}
// One row should have gotten inserted.
EXPECT_EQ(1, CountFoo());
// The second inner one gets rolled back.
{
Transaction inner2(db());
inner2.Begin();
EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
EXPECT_EQ(2, db().GetTransactionNesting());
inner2.Rollback();
EXPECT_EQ(1, db().GetTransactionNesting());
}
// A third inner one will fail in Begin since one has already been rolled
// back.
EXPECT_EQ(1, db().GetTransactionNesting());
{
Transaction inner3(db());
EXPECT_THROW(inner3.Begin(), OrthancException);
EXPECT_EQ(1, db().GetTransactionNesting());
}
}
EXPECT_EQ(0, db().GetTransactionNesting());
EXPECT_EQ(0, CountFoo());
}