extensions/sqlite/driver/SqDriver.cpp
author David Anderson <dvander@alliedmods.net>
Sun Jul 29 14:56:24 2007 +0000 (2007-07-29)
changeset 1210 18e60c8737a2
parent 1203 633c25ce6405
child 1226 478550a4d5ec
permissions -rw-r--r--
fixed an sqlite bug where empty results would count as no result set
dvander@1203
     1
/**
dvander@1203
     2
 * vim: set ts=4 :
dvander@1203
     3
 * ===============================================================
dvander@1203
     4
 * SourceMod SQLite Driver Extension
dvander@1203
     5
 * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved.
dvander@1203
     6
 * ===============================================================
dvander@1203
     7
 *
dvander@1203
     8
 * This program is free software; you can redistribute it and/or
dvander@1203
     9
 * modify it under the terms of the GNU General Public License
dvander@1203
    10
 * as published by the Free Software Foundation; either version 2
dvander@1203
    11
 * of the License, or (at your option) any later version.
dvander@1203
    12
 * This program is distributed in the hope that it will be useful,
dvander@1203
    13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
dvander@1203
    14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
dvander@1203
    15
 * GNU General Public License for more details.
dvander@1203
    16
 * 
dvander@1203
    17
 * You should have received a copy of the GNU General Public License
dvander@1203
    18
 * along with this program; if not, write to the Free Software
dvander@1203
    19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
dvander@1203
    20
 *
dvander@1203
    21
 * Version: $Id$
dvander@1203
    22
 */
dvander@1203
    23
dvander@1203
    24
#include <sm_platform.h>
dvander@1203
    25
#include "extension.h"
dvander@1203
    26
#include "SqDriver.h"
dvander@1203
    27
#include "SqDatabase.h"
dvander@1203
    28
dvander@1203
    29
SqDriver g_SqDriver;
dvander@1203
    30
dvander@1203
    31
unsigned int strncopy(char *dest, const char *src, size_t count)
dvander@1203
    32
{
dvander@1203
    33
	if (!count)
dvander@1203
    34
	{
dvander@1203
    35
		return 0;
dvander@1203
    36
	}
dvander@1203
    37
dvander@1203
    38
	char *start = dest;
dvander@1203
    39
	while ((*src) && (--count))
dvander@1203
    40
	{
dvander@1203
    41
		*dest++ = *src++;
dvander@1203
    42
	}
dvander@1203
    43
	*dest = '\0';
dvander@1203
    44
dvander@1203
    45
	return (dest - start);
dvander@1203
    46
}
dvander@1203
    47
dvander@1203
    48
SqDriver::SqDriver()
dvander@1203
    49
{
dvander@1203
    50
	m_Handle = BAD_HANDLE;
dvander@1203
    51
	m_pOpenLock = NULL;
dvander@1203
    52
}
dvander@1203
    53
dvander@1203
    54
void SqDriver::Initialize()
dvander@1203
    55
{
dvander@1203
    56
	m_pOpenLock = threader->MakeMutex();
dvander@1203
    57
}
dvander@1203
    58
dvander@1203
    59
void SqDriver::Shutdown()
dvander@1203
    60
{
dvander@1203
    61
	if (m_pOpenLock)
dvander@1203
    62
	{
dvander@1203
    63
		m_pOpenLock->DestroyThis();
dvander@1203
    64
	}
dvander@1203
    65
}
dvander@1203
    66
dvander@1203
    67
bool SqDriver::IsThreadSafe()
dvander@1203
    68
{
dvander@1203
    69
	return true;
dvander@1203
    70
}
dvander@1203
    71
dvander@1203
    72
bool SqDriver::InitializeThreadSafety()
dvander@1203
    73
{
dvander@1203
    74
	/* sqlite should be thread safe if the locks are done right.
dvander@1203
    75
	 * we don't enable the "shared cache" because it can corrupt
dvander@1203
    76
	 * open databases!
dvander@1203
    77
	 */
dvander@1203
    78
	return true;
dvander@1203
    79
}
dvander@1203
    80
dvander@1203
    81
void SqDriver::ShutdownThreadSafety()
dvander@1203
    82
{
dvander@1203
    83
	return;
dvander@1203
    84
}
dvander@1203
    85
dvander@1203
    86
IdentityToken_t *SqDriver::GetIdentity()
dvander@1203
    87
{
dvander@1203
    88
	return myself->GetIdentity();
dvander@1203
    89
}
dvander@1203
    90
dvander@1203
    91
const char *SqDriver::GetProductName()
dvander@1203
    92
{
dvander@1203
    93
	return "SQLite";
dvander@1203
    94
}
dvander@1203
    95
dvander@1203
    96
const char *SqDriver::GetIdentifier()
dvander@1203
    97
{
dvander@1203
    98
	return "sqlite";
dvander@1203
    99
}
dvander@1203
   100
dvander@1203
   101
Handle_t SqDriver::GetHandle()
dvander@1203
   102
{
dvander@1203
   103
	if (m_Handle == BAD_HANDLE)
dvander@1203
   104
	{
dvander@1203
   105
		m_Handle = dbi->CreateHandle(DBHandle_Driver, this, myself->GetIdentity());
dvander@1203
   106
	}
dvander@1203
   107
dvander@1203
   108
	return m_Handle;
dvander@1203
   109
}
dvander@1203
   110
dvander@1203
   111
inline bool IsPathSepChar(char c)
dvander@1203
   112
{
dvander@1203
   113
#if defined PLATFORM_WINDOWS
dvander@1203
   114
	return (c == '\\' || c == '/');
dvander@1203
   115
#elif defined PLATFORM_LINUX
dvander@1203
   116
	return (c == '/');
dvander@1203
   117
#endif
dvander@1203
   118
}
dvander@1203
   119
dvander@1203
   120
IDatabase *SqDriver::Connect(const DatabaseInfo *info, bool persistent, char *error, size_t maxlength)
dvander@1203
   121
{
dvander@1203
   122
	/* We wrap most of the open process in a mutex just to be safe */
dvander@1203
   123
	m_pOpenLock->Lock();
dvander@1203
   124
dvander@1203
   125
	/* Format our path */
dvander@1203
   126
	char path[PLATFORM_MAX_PATH];
dvander@1203
   127
	size_t len = libsys->PathFormat(path, sizeof(path), "sqlite/%s", info->database);
dvander@1203
   128
dvander@1203
   129
	/* Chop any filename off */
dvander@1203
   130
	for (size_t i = len-1;
dvander@1203
   131
		 i >= 0 && i <= len-1;
dvander@1203
   132
		 i--)
dvander@1203
   133
	{
dvander@1203
   134
		if (IsPathSepChar(path[i]))
dvander@1203
   135
		{
dvander@1203
   136
			path[i] = '\0';
dvander@1203
   137
			break;
dvander@1203
   138
		}
dvander@1203
   139
	}
dvander@1203
   140
dvander@1203
   141
	/* Test the full path */
dvander@1203
   142
	char fullpath[PLATFORM_MAX_PATH];
dvander@1203
   143
	g_pSM->BuildPath(Path_SM, fullpath, sizeof(fullpath), "data/%s", path);
dvander@1203
   144
	if (!libsys->IsPathDirectory(fullpath))
dvander@1203
   145
	{
dvander@1203
   146
		/* Make sure the data folder exists */
dvander@1203
   147
		len = g_pSM->BuildPath(Path_SM, fullpath, sizeof(fullpath), "data");
dvander@1203
   148
		if (!libsys->IsPathDirectory(fullpath))
dvander@1203
   149
		{
dvander@1203
   150
			if (!libsys->CreateFolder(fullpath))
dvander@1203
   151
			{
dvander@1203
   152
				strncopy(error, "Could not create or open \"data\" folder\"", maxlength);
dvander@1203
   153
				m_pOpenLock->Unlock();
dvander@1203
   154
				return NULL;
dvander@1203
   155
			}
dvander@1203
   156
		}
dvander@1203
   157
dvander@1203
   158
		/* The data folder exists - create each subdir as needed! */
dvander@1203
   159
		char *cur_ptr = path;
dvander@1203
   160
dvander@1203
   161
		do
dvander@1203
   162
		{
dvander@1203
   163
			/* Find the next suitable path */
dvander@1203
   164
			char *next_ptr = cur_ptr;
dvander@1203
   165
			while (*next_ptr != '\0')
dvander@1203
   166
			{
dvander@1203
   167
				if (IsPathSepChar(*next_ptr))
dvander@1203
   168
				{
dvander@1203
   169
					*next_ptr = '\0';
dvander@1203
   170
					next_ptr++;
dvander@1203
   171
					break;
dvander@1203
   172
				}
dvander@1203
   173
				next_ptr++;
dvander@1203
   174
			}
dvander@1203
   175
			if (*next_ptr == '\0')
dvander@1203
   176
			{
dvander@1203
   177
				next_ptr = NULL;
dvander@1203
   178
			}
dvander@1203
   179
			len += libsys->PathFormat(&fullpath[len], sizeof(fullpath)-len, "/%s", cur_ptr);
dvander@1203
   180
			if (!libsys->IsPathDirectory(fullpath) && !libsys->CreateFolder(fullpath))
dvander@1203
   181
			{
dvander@1203
   182
				break;
dvander@1203
   183
			}
dvander@1203
   184
			cur_ptr = next_ptr;
dvander@1203
   185
		} while (cur_ptr);
dvander@1203
   186
	}
dvander@1203
   187
dvander@1203
   188
	/* Build the FINAL path. */
dvander@1203
   189
	g_pSM->BuildPath(Path_SM, fullpath, sizeof(fullpath), "data/sqlite/%s.sq3", info->database);
dvander@1203
   190
dvander@1203
   191
	/* If we're requesting a persistent connection, see if something is already open */
dvander@1203
   192
	if (persistent)
dvander@1203
   193
	{
dvander@1203
   194
		/* See if anything in the cache matches */
dvander@1203
   195
		List<SqDbInfo>::iterator iter;
dvander@1203
   196
		for (iter = m_Cache.begin(); iter != m_Cache.end(); iter++)
dvander@1203
   197
		{
dvander@1203
   198
			if ((*iter).path.compare(fullpath) == 0)
dvander@1203
   199
			{
dvander@1203
   200
				(*iter).db->IncReferenceCount();
dvander@1203
   201
				m_pOpenLock->Unlock();
dvander@1203
   202
				return (*iter).db;
dvander@1203
   203
			}
dvander@1203
   204
		}
dvander@1203
   205
	}
dvander@1203
   206
dvander@1203
   207
	/* Try to open a new connection */
dvander@1203
   208
	sqlite3 *sql;
dvander@1203
   209
	int err = sqlite3_open(fullpath, &sql);
dvander@1203
   210
	if (err != SQLITE_OK)
dvander@1203
   211
	{
dvander@1203
   212
		strncopy(error, sqlite3_errmsg(sql), maxlength);
dvander@1203
   213
		sqlite3_close(sql);
dvander@1203
   214
		m_pOpenLock->Unlock();
dvander@1203
   215
		return NULL;
dvander@1203
   216
	}
dvander@1203
   217
dvander@1203
   218
	SqDatabase *pdb = new SqDatabase(sql, persistent);
dvander@1203
   219
dvander@1203
   220
	if (persistent)
dvander@1203
   221
	{
dvander@1203
   222
		SqDbInfo pinfo;
dvander@1203
   223
		pinfo.path = fullpath;
dvander@1203
   224
		pinfo.db = pdb;
dvander@1203
   225
		m_Cache.push_back(pinfo);
dvander@1203
   226
	}
dvander@1203
   227
dvander@1203
   228
	m_pOpenLock->Unlock();
dvander@1203
   229
dvander@1203
   230
	return pdb;
dvander@1203
   231
}
dvander@1203
   232
dvander@1203
   233
void SqDriver::RemovePersistent(IDatabase *pdb)
dvander@1203
   234
{
dvander@1203
   235
	List<SqDbInfo>::iterator iter;
dvander@1203
   236
	for (iter = m_Cache.begin(); iter != m_Cache.end(); iter++)
dvander@1203
   237
	{
dvander@1203
   238
		if ((*iter).db == pdb)
dvander@1203
   239
		{
dvander@1203
   240
			iter = m_Cache.erase(iter);
dvander@1203
   241
			return;
dvander@1203
   242
		}
dvander@1203
   243
	}
dvander@1203
   244
}