2025-06-04 23:10:11 +09:00
//
// Copyright (c) 2009-2012 Krueger Systems, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
2025-06-06 02:17:54 +09:00
#nullable enable
2025-06-04 23:10:11 +09:00
#if WINDOWS_PHONE & & ! USE_WP8_NATIVE_SQLITE
#define USE_CSHARP_SQLITE
#endif
2025-06-06 02:17:54 +09:00
2025-06-04 23:10:11 +09:00
using System ;
using System.Diagnostics ;
using System.Runtime.InteropServices ;
using System.Collections.Generic ;
using System.Reflection ;
using System.Linq ;
using System.Linq.Expressions ;
using System.Threading ;
#if USE_CSHARP_SQLITE
using Sqlite3 = Community . CsharpSqlite . Sqlite3 ;
using Sqlite3DatabaseHandle = Community . CsharpSqlite . Sqlite3 . sqlite3 ;
using Sqlite3Statement = Community . CsharpSqlite . Sqlite3 . Vdbe ;
#elif USE_WP8_NATIVE_SQLITE
using Sqlite3 = Sqlite . Sqlite3 ;
using Sqlite3DatabaseHandle = Sqlite . Database ;
using Sqlite3Statement = Sqlite . Statement ;
#else
using Sqlite3DatabaseHandle = System . IntPtr ;
using Sqlite3Statement = System . IntPtr ;
#endif
/// <summary>
2025-08-11 18:49:20 +09:00
/// SQLite4Unity3d는 Unity 환경에서 SQLite를 쉽게 사용할 수 있게 해주는 라이브러리입니다.
/// 이 클래스는 SQLite 데이터베이스에 대한 연결을 관리합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
namespace SQLite4Unity3d
{
/// <summary>
2025-08-11 18:49:20 +09:00
/// SQLite 작업 중 발생하는 예외를 처리하기 위한 클래스입니다.
2025-06-04 23:10:11 +09:00
/// </summary>
public class SQLiteException : Exception
{
public SQLite3 . Result Result { get ; private set ; }
protected SQLiteException ( SQLite3 . Result r , string message ) : base ( message )
{
Result = r ;
}
public static SQLiteException New ( SQLite3 . Result r , string message )
{
return new SQLiteException ( r , message ) ;
}
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// NOT NULL 제약 조건 위반 시 발생하는 예외를 처리하기 위한 클래스입니다.
2025-06-04 23:10:11 +09:00
/// </summary>
public class NotNullConstraintViolationException : SQLiteException
{
public IEnumerable < TableMapping . Column > Columns { get ; protected set ; }
protected NotNullConstraintViolationException ( SQLite3 . Result r , string message )
: this ( r , message , null , null )
{
}
protected NotNullConstraintViolationException ( SQLite3 . Result r , string message , TableMapping mapping , object obj )
: base ( r , message )
{
if ( mapping ! = null & & obj ! = null )
{
this . Columns = from c in mapping . Columns
where c . IsNullable = = false & & c . GetValue ( obj ) = = null
select c ;
}
}
public static new NotNullConstraintViolationException New ( SQLite3 . Result r , string message )
{
return new NotNullConstraintViolationException ( r , message ) ;
}
public static NotNullConstraintViolationException New ( SQLite3 . Result r , string message , TableMapping mapping , object obj )
{
return new NotNullConstraintViolationException ( r , message , mapping , obj ) ;
}
public static NotNullConstraintViolationException New ( SQLiteException exception , TableMapping mapping , object obj )
{
return new NotNullConstraintViolationException ( exception . Result , exception . Message , mapping , obj ) ;
}
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// SQLite 데이터베이스 파일을 열기 위한 옵션들을 정의합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
[Flags]
public enum SQLiteOpenFlags
{
ReadOnly = 1 , ReadWrite = 2 , Create = 4 ,
NoMutex = 0x8000 , FullMutex = 0x10000 ,
SharedCache = 0x20000 , PrivateCache = 0x40000 ,
ProtectionComplete = 0x00100000 ,
ProtectionCompleteUnlessOpen = 0x00200000 ,
ProtectionCompleteUntilFirstUserAuthentication = 0x00300000 ,
ProtectionNone = 0x00400000
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 테이블 생성 시 사용되는 플래그입니다.
2025-06-04 23:10:11 +09:00
/// </summary>
[Flags]
public enum CreateFlags
{
None = 0 ,
ImplicitPK = 1 , // create a primary key for field called 'Id' (Orm.ImplicitPkName)
ImplicitIndex = 2 , // create an index for fields ending in 'Id' (Orm.ImplicitIndexSuffix)
AllImplicit = 3 , // do both above
AutoIncPK = 4 // force PK field to be auto inc
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// SQLite 데이터베이스에 대한 열린 연결을 나타냅니다.
2025-06-04 23:10:11 +09:00
/// </summary>
public partial class SQLiteConnection : IDisposable
{
private bool _open ;
private TimeSpan _busyTimeout ;
private Dictionary < string , TableMapping > _mappings = null ;
private Dictionary < string , TableMapping > _tables = null ;
private System . Diagnostics . Stopwatch _sw ;
private TimeSpan _elapsed = default ( TimeSpan ) ;
private int _transactionDepth = 0 ;
private Random _rand = new Random ( ) ;
public Sqlite3DatabaseHandle Handle { get ; private set ; }
internal static readonly Sqlite3DatabaseHandle NullHandle = default ( Sqlite3DatabaseHandle ) ;
public string DatabasePath { get ; private set ; }
2025-08-11 18:49:20 +09:00
// 동기화 객체의 사전입니다.
2025-06-04 23:10:11 +09:00
//
2025-08-11 18:49:20 +09:00
// 데이터베이스 파일 손상을 방지하기 위해 데이터베이스 파일에 *동기적으로* 접근해야 합니다.
// 이를 위해 각 데이터베이스 파일에 대한 동기화 객체를 생성하고 모든 연결 간에 공유하기 위해
// 정적 사전에 저장합니다.
// 사전의 키는 데이터베이스 파일 경로이고, 값은 lock() 문에서 사용할 객체입니다.
2025-06-04 23:10:11 +09:00
//
2025-08-11 18:49:20 +09:00
// 사용 사례:
// - 데이터베이스 파일 잠금은 암시적으로 자동으로 이루어집니다.
// - 교착 상태를 방지하기 위해 응용 프로그램은 다음과 같은 방법으로 명시적으로 데이터베이스 파일을 잠글 수 있습니다:
// - RunInTransaction(Action)은 트랜잭션 중에 데이터베이스를 잠급니다(삽입/업데이트용).
// - RunInDatabaseLock(Action)은 비슷하게 데이터베이스를 잠그지만 트랜잭션은 없습니다(쿼리용).
2025-06-04 23:10:11 +09:00
private static Dictionary < string , object > syncObjects = new Dictionary < string , object > ( ) ;
#region debug tracing
public bool Trace { get ; set ; }
public bool TimeExecution { get ; set ; }
public delegate void TraceHandler ( string message ) ;
public event TraceHandler TraceEvent ;
internal void InvokeTrace ( string message )
{
if ( TraceEvent ! = null )
{
TraceEvent ( message ) ;
}
}
public delegate void TimeExecutionHandler ( TimeSpan executionTime , TimeSpan totalExecutionTime ) ;
public event TimeExecutionHandler TimeExecutionEvent ;
internal void InvokeTimeExecution ( TimeSpan executionTime , TimeSpan totalExecutionTime )
{
if ( TimeExecutionEvent ! = null )
{
TimeExecutionEvent ( executionTime , totalExecutionTime ) ;
}
}
#endregion
public bool StoreDateTimeAsTicks { get ; private set ; }
/// <summary>
2025-08-11 18:49:20 +09:00
/// 새로운 SQLiteConnection을 생성하고 지정된 경로의 SQLite 데이터베이스를 엽니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="databasePath">
2025-08-11 18:49:20 +09:00
/// 데이터베이스 파일의 경로를 지정합니다.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="storeDateTimeAsTicks">
2025-08-11 18:49:20 +09:00
/// DateTime 속성을 틱(true) 또는 문자열(false)로 저장할지 지정합니다.
/// 새 프로젝트에서는 반드시 true로 설정하는 것이 좋습니다. false는 이전 버전과의 호환성을 위한 것입니다.
/// true로 설정하면 성능이 크게 향상되며 단점이 없습니다.
2025-06-04 23:10:11 +09:00
/// </param>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 기본 데이터베이스 연결 생성
2025-06-04 23:10:11 +09:00
/// var db = new SQLiteConnection(Application.persistentDataPath + "/myDatabase.db", true);
///
2025-08-11 18:49:20 +09:00
/// // 테이블 생성
2025-06-10 01:09:36 +09:00
/// db.CreateTable<Person>();
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public SQLiteConnection ( string databasePath , bool storeDateTimeAsTicks = false )
: this ( databasePath , SQLiteOpenFlags . ReadWrite | SQLiteOpenFlags . Create , storeDateTimeAsTicks )
{
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 새로운 SQLiteConnection을 생성하고 지정된 경로의 SQLite 데이터베이스를 엽니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="databasePath">
2025-08-11 18:49:20 +09:00
/// 데이터베이스 파일의 경로를 지정합니다.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="openFlags">
2025-08-11 18:49:20 +09:00
/// 데이터베이스를 여는 방법을 지정하는 플래그입니다.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="storeDateTimeAsTicks">
2025-08-11 18:49:20 +09:00
/// DateTime 속성을 틱(true) 또는 문자열(false)로 저장할지 지정합니다.
/// 새 프로젝트에서는 반드시 true로 설정하는 것이 좋습니다. false는 이전 버전과의 호환성을 위한 것입니다.
/// true로 설정하면 성능이 크게 향상되며 단점이 없습니다.
2025-06-04 23:10:11 +09:00
/// </param>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 읽기 전용 모드로 데이터베이스 열기
2025-06-04 23:10:11 +09:00
/// var dbReadOnly = new SQLiteConnection(
/// Application.persistentDataPath + "/myDatabase.db",
/// SQLiteOpenFlags.ReadOnly,
/// true);
/// </code>
/// </example>
public SQLiteConnection ( string databasePath , SQLiteOpenFlags openFlags , bool storeDateTimeAsTicks = false )
{
if ( string . IsNullOrEmpty ( databasePath ) )
throw new ArgumentException ( "Must be specified" , "databasePath" ) ;
DatabasePath = databasePath ;
mayCreateSyncObject ( databasePath ) ;
#if NETFX_CORE
SQLite3 . SetDirectory ( /*temp directory type*/ 2 , Windows . Storage . ApplicationData . Current . TemporaryFolder . Path ) ;
#endif
Sqlite3DatabaseHandle handle ;
#if SILVERLIGHT | | USE_CSHARP_SQLITE
var r = SQLite3 . Open ( databasePath , out handle , ( int ) openFlags , IntPtr . Zero ) ;
#else
// open using the byte[]
// in the case where the path may include Unicode
// force open to using UTF-8 using sqlite3_open_v2
var databasePathAsBytes = GetNullTerminatedUtf8 ( DatabasePath ) ;
var r = SQLite3 . Open ( databasePathAsBytes , out handle , ( int ) openFlags , IntPtr . Zero ) ;
#endif
Handle = handle ;
if ( r ! = SQLite3 . Result . OK )
{
throw SQLiteException . New ( r , String . Format ( "Could not open database file: {0} ({1})" , DatabasePath , r ) ) ;
}
_open = true ;
StoreDateTimeAsTicks = storeDateTimeAsTicks ;
BusyTimeout = TimeSpan . FromSeconds ( 0.1 ) ;
}
static SQLiteConnection ( )
{
if ( _preserveDuringLinkMagic )
{
var ti = new ColumnInfo ( ) ;
ti . Name = "magic" ;
}
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 주어진 데이터베이스 경로에 대한 동기화 객체를 생성합니다(아직 존재하지 않는 경우).
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <param name="databasePath">데이터베이스 파일 경로</param>
2025-06-04 23:10:11 +09:00
void mayCreateSyncObject ( string databasePath )
{
if ( ! syncObjects . ContainsKey ( databasePath ) )
{
syncObjects [ databasePath ] = new object ( ) ;
}
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 데이터베이스 파일을 업데이트하기 위한 동기화 객체를 가져옵니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <value>동기화 객체.</value>
2025-06-04 23:10:11 +09:00
public object SyncObject { get { return syncObjects [ DatabasePath ] ; } }
/// <summary>
2025-08-11 18:49:20 +09:00
/// 확장 기능 로드를 활성화 또는 비활성화합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <param name="onoff">1이면 활성화, 0이면 비활성화</param>
2025-06-04 23:10:11 +09:00
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // SQLite 확장 기능 활성화
2025-06-04 23:10:11 +09:00
/// db.EnableLoadExtension(1);
/// </code>
/// </example>
public void EnableLoadExtension ( int onoff )
{
SQLite3 . Result r = SQLite3 . EnableLoadExtension ( Handle , onoff ) ;
if ( r ! = SQLite3 . Result . OK )
{
string msg = SQLite3 . GetErrmsg ( Handle ) ;
throw SQLiteException . New ( r , msg ) ;
}
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 문자열을 UTF-8로 인코딩하고 NULL 종료 바이트를 추가합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <param name="s">인코딩할 문자열</param>
/// <returns>NULL 종료된 UTF-8 바이트 배열</returns>
2025-06-04 23:10:11 +09:00
static byte [ ] GetNullTerminatedUtf8 ( string s )
{
var utf8Length = System . Text . Encoding . UTF8 . GetByteCount ( s ) ;
var bytes = new byte [ utf8Length + 1 ] ;
utf8Length = System . Text . Encoding . UTF8 . GetBytes ( s , 0 , s . Length , bytes , 0 ) ;
return bytes ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// MonoTouch 링커가 볼 수 있지만 실행하지 않기를 바라는 코드를 나열하는 데 사용됩니다.
2025-06-04 23:10:11 +09:00
/// </summary>
#pragma warning disable 649
static bool _preserveDuringLinkMagic ;
#pragma warning restore 649
/// <summary>
2025-08-11 18:49:20 +09:00
/// 테이블이 잠겼을 때 지정된 시간 동안 대기하는 비지 핸들러를 설정합니다.
/// 핸들러는 <see cref="BusyTimeout"/>의 총 시간이 누적될 때까지 여러 번 대기합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 대기 시간 설정
2025-06-04 23:10:11 +09:00
/// db.BusyTimeout = TimeSpan.FromSeconds(5);
/// </code>
/// </example>
public TimeSpan BusyTimeout
{
get { return _busyTimeout ; }
set
{
_busyTimeout = value ;
if ( Handle ! = NullHandle )
{
SQLite3 . BusyTimeout ( Handle , ( int ) _busyTimeout . TotalMilliseconds ) ;
}
}
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 연결이 현재 이해하는 타입에서 테이블로의 매핑을 반환합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
public IEnumerable < TableMapping > TableMappings
{
get
{
return _tables ! = null ? _tables . Values : Enumerable . Empty < TableMapping > ( ) ;
}
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 주어진 타입에 대해 자동 생성된 매핑을 검색합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="type">
2025-08-11 18:49:20 +09:00
/// 데이터베이스에 대한 매핑이 반환되는 타입입니다.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="createFlags">
2025-08-11 18:49:20 +09:00
/// 명명 규칙에 따라 암시적 PK와 인덱스를 허용하는 선택적 플래그
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 매핑은 데이터베이스 열의 스키마를 나타내며 객체의 속성을 설정하고
/// 가져오는 메서드를 포함합니다.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // Person 클래스에 대한 매핑 얻기
2025-06-04 23:10:11 +09:00
/// var mapping = db.GetMapping(typeof(Person));
///
2025-08-11 18:49:20 +09:00
/// // 매핑의 테이블 이름 확인
/// Console.WriteLine("테이블 이름: " + mapping.TableName);
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public TableMapping GetMapping ( Type type , CreateFlags createFlags = CreateFlags . None )
{
if ( _mappings = = null )
{
_mappings = new Dictionary < string , TableMapping > ( ) ;
}
TableMapping map ;
if ( ! _mappings . TryGetValue ( type . FullName , out map ) )
{
map = new TableMapping ( type , createFlags ) ;
_mappings [ type . FullName ] = map ;
}
return map ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 주어진 타입에 대해 자동 생성된 매핑을 검색합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 매핑은 데이터베이스 열의 스키마를 나타내며 객체의 속성을 설정하고
/// 가져오는 메서드를 포함합니다.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 제네릭 타입으로 매핑 얻기
2025-06-10 01:09:36 +09:00
/// var mapping = db.GetMapping<Person>();
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public TableMapping GetMapping < T > ( )
{
return GetMapping ( typeof ( T ) ) ;
}
private struct IndexedColumn
{
public int Order ;
public string ColumnName ;
}
private struct IndexInfo
{
public string IndexName ;
public string TableName ;
public bool Unique ;
public List < IndexedColumn > Columns ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 데이터베이스에서 "drop table" 명령을 실행합니다. 이 작업은 복구할 수 없습니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // Person 테이블 삭제
2025-06-10 01:09:36 +09:00
/// db.DropTable<Person>();
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public int DropTable < T > ( )
{
var map = GetMapping ( typeof ( T ) ) ;
var query = string . Format ( "drop table if exists \"{0}\"" , map . TableName ) ;
return Execute ( query ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 데이터베이스에서 "create table if not exists" 명령을 실행합니다.
/// 또한 테이블 열에 지정된 인덱스를 생성합니다.
/// 지정된 타입에서 자동으로 생성된 스키마를 사용합니다.
/// 나중에 GetMapping을 호출하여 이 스키마에 액세스할 수 있습니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 데이터베이스 스키마에 추가된 항목의 수를 반환합니다.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // Person 클래스를 표현하는 테이블 생성
2025-06-10 01:09:36 +09:00
/// db.CreateTable<Person>();
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 암시적 인덱스 생성 옵션 사용
2025-06-10 01:09:36 +09:00
/// db.CreateTable<Person>(CreateFlags.ImplicitIndex);
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public int CreateTable < T > ( CreateFlags createFlags = CreateFlags . None )
{
return CreateTable ( typeof ( T ) , createFlags ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 데이터베이스에서 "create table if not exists" 명령을 실행합니다.
/// 또한 테이블 열에 지정된 인덱스를 생성합니다.
/// 지정된 타입에서 자동으로 생성된 스키마를 사용합니다.
/// 나중에 GetMapping을 호출하여 이 스키마에 액세스할 수 있습니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <param name="ty">데이터베이스 테이블로 반영할 타입입니다.</param>
/// <param name="createFlags">명명 규칙에 따른 암시적 PK 및 인덱스를 허용하는 선택적 플래그입니다.</param>
2025-06-04 23:10:11 +09:00
/// <returns>
2025-08-11 18:49:20 +09:00
/// 데이터베이스 스키마에 추가된 항목의 수를 반환합니다.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // Type 객체를 사용하여 테이블 생성
2025-06-04 23:10:11 +09:00
/// db.CreateTable(typeof(Person), CreateFlags.AutoIncPK);
/// </code>
/// </example>
public int CreateTable ( Type ty , CreateFlags createFlags = CreateFlags . None )
{
if ( _tables = = null )
{
_tables = new Dictionary < string , TableMapping > ( ) ;
}
TableMapping map ;
if ( ! _tables . TryGetValue ( ty . FullName , out map ) )
{
map = GetMapping ( ty , createFlags ) ;
_tables . Add ( ty . FullName , map ) ;
}
var query = "create table if not exists \"" + map . TableName + "\"(\n" ;
var decls = map . Columns . Select ( p = > Orm . SqlDecl ( p , StoreDateTimeAsTicks ) ) ;
var decl = string . Join ( ",\n" , decls . ToArray ( ) ) ;
query + = decl ;
query + = ")" ;
var count = Execute ( query ) ;
if ( count = = 0 )
{ //Possible bug: This always seems to return 0?
// Table already exists, migrate it
MigrateTable ( map ) ;
}
var indexes = new Dictionary < string , IndexInfo > ( ) ;
foreach ( var c in map . Columns )
{
foreach ( var i in c . Indices )
{
var iname = i . Name ? ? map . TableName + "_" + c . Name ;
IndexInfo iinfo ;
if ( ! indexes . TryGetValue ( iname , out iinfo ) )
{
iinfo = new IndexInfo
{
IndexName = iname ,
TableName = map . TableName ,
Unique = i . Unique ,
Columns = new List < IndexedColumn > ( )
} ;
indexes . Add ( iname , iinfo ) ;
}
if ( i . Unique ! = iinfo . Unique )
throw new Exception ( "All the columns in an index must have the same value for their Unique property" ) ;
iinfo . Columns . Add ( new IndexedColumn
{
Order = i . Order ,
ColumnName = c . Name
} ) ;
}
}
foreach ( var indexName in indexes . Keys )
{
var index = indexes [ indexName ] ;
string [ ] columnNames = new string [ index . Columns . Count ] ;
if ( index . Columns . Count = = 1 )
{
columnNames [ 0 ] = index . Columns [ 0 ] . ColumnName ;
}
else
{
index . Columns . Sort ( ( lhs , rhs ) = > {
return lhs . Order - rhs . Order ;
} ) ;
for ( int i = 0 , end = index . Columns . Count ; i < end ; + + i )
{
columnNames [ i ] = index . Columns [ i ] . ColumnName ;
}
}
count + = CreateIndex ( indexName , index . TableName , columnNames , index . Unique ) ;
}
return count ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 테이블과 열에 대한 인덱스를 생성합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <param name="indexName">생성할 인덱스의 이름</param>
/// <param name="tableName">데이터베이스 테이블 이름</param>
/// <param name="columnNames">인덱싱할 열 이름 배열</param>
/// <param name="unique">인덱스가 고유해야 하는지 여부</param>
2025-06-04 23:10:11 +09:00
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 여러 열에 대한 고유 인덱스 생성
2025-06-04 23:10:11 +09:00
/// db.CreateIndex("personNameIndex", "Person", new[] { "FirstName", "LastName" }, true);
/// </code>
/// </example>
public int CreateIndex ( string indexName , string tableName , string [ ] columnNames , bool unique = false )
{
const string sqlFormat = "create {2} index if not exists \"{3}\" on \"{0}\"(\"{1}\")" ;
var sql = String . Format ( sqlFormat , tableName , string . Join ( "\", \"" , columnNames ) , unique ? "unique" : "" , indexName ) ;
return Execute ( sql ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 테이블과 열에 대한 인덱스를 생성합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <param name="indexName">생성할 인덱스의 이름</param>
/// <param name="tableName">데이터베이스 테이블 이름</param>
/// <param name="columnName">인덱싱할 열 이름</param>
/// <param name="unique">인덱스가 고유해야 하는지 여부</param>
2025-06-04 23:10:11 +09:00
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 단일 열에 대한 인덱스 생성
2025-06-04 23:10:11 +09:00
/// db.CreateIndex("emailIndex", "Person", "Email", true);
/// </code>
/// </example>
public int CreateIndex ( string indexName , string tableName , string columnName , bool unique = false )
{
return CreateIndex ( indexName , tableName , new string [ ] { columnName } , unique ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 테이블과 열에 대한 인덱스를 생성합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <param name="tableName">데이터베이스 테이블 이름</param>
/// <param name="columnName">인덱싱할 열 이름</param>
/// <param name="unique">인덱스가 고유해야 하는지 여부</param>
2025-06-04 23:10:11 +09:00
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 테이블과 열 이름에서 인덱스 이름 자동 생성
2025-06-04 23:10:11 +09:00
/// db.CreateIndex("Person", "Age", false);
2025-08-11 18:49:20 +09:00
/// // 생성된 인덱스 이름은 "Person_Age"
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public int CreateIndex ( string tableName , string columnName , bool unique = false )
{
return CreateIndex ( tableName + "_" + columnName , tableName , columnName , unique ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 테이블과 열에 대한 인덱스를 생성합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <param name="tableName">데이터베이스 테이블 이름</param>
/// <param name="columnNames">인덱싱할 열 이름 배열</param>
/// <param name="unique">인덱스가 고유해야 하는지 여부</param>
2025-06-04 23:10:11 +09:00
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 테이블과 여러 열 이름에서 인덱스 이름 자동 생성
2025-06-04 23:10:11 +09:00
/// db.CreateIndex("Person", new[] { "FirstName", "LastName" }, true);
2025-08-11 18:49:20 +09:00
/// // 생성된 인덱스 이름은 "Person_FirstName_LastName"
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public int CreateIndex ( string tableName , string [ ] columnNames , bool unique = false )
{
return CreateIndex ( tableName + "_" + string . Join ( "_" , columnNames ) , tableName , columnNames , unique ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 객체 속성에 대한 인덱스를 생성합니다.
/// 예시: CreateIndex<Client>(c => c.Name);
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <typeparam name="T">데이터베이스 테이블에 반영할 타입.</typeparam>
/// <param name="property">인덱싱할 속성</param>
/// <param name="unique">인덱스가 고유해야 하는지 여부</param>
2025-06-04 23:10:11 +09:00
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 람다 식을 사용하여 속성에 대한 인덱스 생성
2025-06-10 01:09:36 +09:00
/// db.CreateIndex<Person>(p => p.Email, true);
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public void CreateIndex < T > ( Expression < Func < T , object > > property , bool unique = false )
{
MemberExpression mx ;
if ( property . Body . NodeType = = ExpressionType . Convert )
{
mx = ( ( UnaryExpression ) property . Body ) . Operand as MemberExpression ;
}
else
{
mx = ( property . Body as MemberExpression ) ;
}
var propertyInfo = mx . Member as PropertyInfo ;
if ( propertyInfo = = null )
{
throw new ArgumentException ( "The lambda expression 'property' should point to a valid Property" ) ;
}
var propName = propertyInfo . Name ;
var map = GetMapping < T > ( ) ;
var colName = map . FindColumnWithPropertyName ( propName ) . Name ;
CreateIndex ( map . TableName , colName , unique ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 데이터베이스 테이블 열에 대한 정보를 저장하는 클래스입니다.
2025-06-04 23:10:11 +09:00
/// </summary>
public class ColumnInfo
{
// public int cid { get; set; }
/// <summary>
2025-08-11 18:49:20 +09:00
/// 열의 이름입니다.
2025-06-04 23:10:11 +09:00
/// </summary>
[Column("name")]
public string Name { get ; set ; }
// [Column ("type")]
// public string ColumnType { get; set; }
/// <summary>
2025-08-11 18:49:20 +09:00
/// NULL 값 허용 여부를 나타냅니다. 0이면 NULL 허용, 1이면 NOT NULL입니다.
2025-06-04 23:10:11 +09:00
/// </summary>
public int notnull { get ; set ; }
// public string dflt_value { get; set; }
// public int pk { get; set; }
public override string ToString ( )
{
return Name ;
}
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 테이블의 열 정보를 가져옵니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <param name="tableName">테이블의 이름</param>
/// <returns>테이블의 모든 열에 대한 정보 목록</returns>
2025-06-04 23:10:11 +09:00
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // Person 테이블의 열 정보 가져오기
2025-06-04 23:10:11 +09:00
/// var columns = db.GetTableInfo("Person");
/// foreach (var column in columns) {
2025-08-11 18:49:20 +09:00
/// Console.WriteLine("열 이름: " + column.Name);
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public List < ColumnInfo > GetTableInfo ( string tableName )
{
var query = "pragma table_info(\"" + tableName + "\")" ;
return Query < ColumnInfo > ( query ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 기존 테이블 구조를 현재 매핑된 객체 구조와 일치하도록 업데이트합니다.
/// 새로운 열이 있으면 테이블에 추가합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <param name="map">테이블 매핑 정보</param>
2025-06-04 23:10:11 +09:00
void MigrateTable ( TableMapping map )
{
var existingCols = GetTableInfo ( map . TableName ) ;
var toBeAdded = new List < TableMapping . Column > ( ) ;
foreach ( var p in map . Columns )
{
var found = false ;
foreach ( var c in existingCols )
{
found = ( string . Compare ( p . Name , c . Name , StringComparison . OrdinalIgnoreCase ) = = 0 ) ;
if ( found )
break ;
}
if ( ! found )
{
toBeAdded . Add ( p ) ;
}
}
foreach ( var p in toBeAdded )
{
var addCol = "alter table \"" + map . TableName + "\" add column " + Orm . SqlDecl ( p , StoreDateTimeAsTicks ) ;
Execute ( addCol ) ;
}
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 새로운 SQLiteCommand를 생성합니다. 서브 클래스 제공을 위해 재정의할 수 있습니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <seealso cref="SQLiteCommand.OnInstanceCreated"/>
protected virtual SQLiteCommand NewCommand ( )
{
return new SQLiteCommand ( this ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 인수가 있는 명령 텍스트로 새 SQLiteCommand를 생성합니다.
/// 명령 텍스트에서 각 인수에 대해 '?' 를 배치합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="cmdText">
2025-08-11 18:49:20 +09:00
/// 완전히 이스케이프된 SQL.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="args">
2025-08-11 18:49:20 +09:00
/// 명령 텍스트에서 '?'의 출현을 대체하는 인수.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// <see cref="SQLiteCommand"/> 객체
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 파라미터가 있는 명령 생성
2025-06-04 23:10:11 +09:00
/// var cmd = db.CreateCommand("SELECT * FROM Person WHERE Id = ?", 1);
2025-06-10 01:09:36 +09:00
/// var person = cmd.ExecuteQuery<Person>().FirstOrDefault();
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public SQLiteCommand CreateCommand ( string cmdText , params object [ ] ps )
{
if ( ! _open )
throw SQLiteException . New ( SQLite3 . Result . Error , "Cannot create commands from unopened database" ) ;
var cmd = NewCommand ( ) ;
cmd . CommandText = cmdText ;
foreach ( var o in ps )
{
cmd . Bind ( o ) ;
}
return cmd ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 인수가 있는 명령 텍스트(SQL)로 SQLiteCommand를 생성하고 실행합니다.
/// 명령 텍스트에서 각 인수에 대해 '?'를 배치한 다음 해당 명령을 실행합니다.
/// 행이 반환될 것으로 예상하지 않을 때 Query 대신 이 메서드를 사용하세요.
/// 이러한 경우에는 INSERT, UPDATE 및 DELETE가 포함됩니다.
/// 연결의 Trace 또는 TimeExecution 속성을 설정하여 실행을 프로파일링할 수 있습니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="query">
2025-08-11 18:49:20 +09:00
/// 완전히 이스케이프된 SQL.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="args">
2025-08-11 18:49:20 +09:00
/// 쿼리에서 '?'의 출현을 대체하는 인수.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 이 실행의 결과로 데이터베이스에서 수정된 행 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // SQL 쿼리 직접 실행
/// int rowsAffected = db.Start("UPDATE Person SET Name = ? WHERE Id = ?", "홍길동", 1);
/// Console.WriteLine($"업데이트된 행 수: {rowsAffected}");
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public int Execute ( string query , params object [ ] args )
{
var cmd = CreateCommand ( query , args ) ;
if ( TimeExecution )
{
if ( _sw = = null )
{
_sw = new Stopwatch ( ) ;
}
_sw . Reset ( ) ;
_sw . Start ( ) ;
}
var r = cmd . ExecuteNonQuery ( ) ;
if ( TimeExecution )
{
_sw . Stop ( ) ;
_elapsed + = _sw . Elapsed ;
this . InvokeTimeExecution ( _sw . Elapsed , _elapsed ) ;
}
return r ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// SQL 쿼리를 실행하고 스칼라 결과를 반환합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <typeparam name="T">반환되는 스칼라 결과의 타입</typeparam>
/// <param name="query">실행할 SQL 쿼리</param>
/// <param name="args">쿼리 파라미터</param>
/// <returns>쿼리 결과의 첫 번째 열, 첫 번째 행 값</returns>
2025-06-04 23:10:11 +09:00
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 스칼라 값 가져오기
2025-06-10 01:09:36 +09:00
/// int count = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
/// string name = db.ExecuteScalar<string>("SELECT Name FROM Person WHERE Id = ?", 1);
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public T ExecuteScalar < T > ( string query , params object [ ] args )
{
var cmd = CreateCommand ( query , args ) ;
if ( TimeExecution )
{
if ( _sw = = null )
{
_sw = new Stopwatch ( ) ;
}
_sw . Reset ( ) ;
_sw . Start ( ) ;
}
var r = cmd . ExecuteScalar < T > ( ) ;
if ( TimeExecution )
{
_sw . Stop ( ) ;
_elapsed + = _sw . Elapsed ;
this . InvokeTimeExecution ( _sw . Elapsed , _elapsed ) ;
}
return r ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 인수가 있는 명령 텍스트(SQL)로 SQLiteCommand를 생성하고 실행합니다.
/// 명령 텍스트에서 각 인수에 대해 '?'를 배치한 다음 해당 명령을 실행합니다.
/// 지정된 타입에 대해 자동으로 생성된 매핑을 사용하여 각 결과 행을 반환합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="query">
2025-08-11 18:49:20 +09:00
/// 완전히 이스케이프된 SQL.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="args">
2025-08-11 18:49:20 +09:00
/// 쿼리에서 '?'의 출현을 대체하는 인수.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 쿼리에 의해 반환된 각 행에 대해 하나의 결과를 갖는 열거 가능.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 조건에 맞는 모든 Person 객체 조회
2025-06-10 01:09:36 +09:00
/// var adults = db.Query<Person>("SELECT * FROM Person WHERE Age >= ?", 18);
2025-06-04 23:10:11 +09:00
/// foreach (var person in adults) {
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"성인: {person.Name}, {person.Age}세");
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public List < T > Query < T > ( string query , params object [ ] args ) where T : new ( )
{
var cmd = CreateCommand ( query , args ) ;
return cmd . ExecuteQuery < T > ( ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 인수가 있는 명령 텍스트(SQL)로 SQLiteCommand를 생성하고 실행합니다.
/// 명령 텍스트에서 각 인수에 대해 '?'를 배치한 다음 해당 명령을 실행합니다.
/// 지정된 타입에 대해 자동으로 생성된 매핑을 사용하여 각 결과 행을 반환합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="query">
2025-08-11 18:49:20 +09:00
/// 완전히 이스케이프된 SQL.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="args">
2025-08-11 18:49:20 +09:00
/// 쿼리에서 '?'의 출현을 대체하는 인수.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 쿼리에 의해 반환된 각 행에 대해 하나의 결과를 갖는 열거 가능.
/// 열거자는 MoveNext를 호출할 때마다 sqlite3_step을 호출하므로,
/// 열거자의 수명 동안 데이터베이스 연결이 열려 있어야 합니다.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 대용량 데이터를 지연 로딩으로 처리
2025-06-10 01:09:36 +09:00
/// var query = db.DeferredQuery<Person>("SELECT * FROM Person");
2025-06-04 23:10:11 +09:00
/// using (var enumerator = query.GetEnumerator()) {
/// while (enumerator.MoveNext()) {
/// var person = enumerator.Current;
/// ProcessPerson(person);
/// }
/// }
/// </code>
/// </example>
public IEnumerable < T > DeferredQuery < T > ( string query , params object [ ] args ) where T : new ( )
{
var cmd = CreateCommand ( query , args ) ;
return cmd . ExecuteDeferredQuery < T > ( ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 인수가 있는 명령 텍스트(SQL)로 SQLiteCommand를 생성하고 실행합니다.
/// 명령 텍스트에서 각 인수에 대해 '?'를 배치한 다음 해당 명령을 실행합니다.
/// 지정된 매핑을 사용하여 각 결과 행을 반환합니다.
/// 이 함수는 내성(introspection)을 통해 데이터베이스를 쿼리하기 위해
/// 라이브러리에서만 사용됩니다. 일반적으로 사용되지 않습니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="map">
2025-08-11 18:49:20 +09:00
/// 결과 행을 객체로 변환하는 데 사용할 <see cref="TableMapping"/>.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="query">
2025-08-11 18:49:20 +09:00
/// 완전히 이스케이프된 SQL.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="args">
2025-08-11 18:49:20 +09:00
/// 쿼리에서 '?'의 출현을 대체하는 인수.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 쿼리에 의해 반환된 각 행에 대해 하나의 결과를 갖는 열거 가능.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 매핑 객체를 사용해 쿼리 실행
2025-06-04 23:10:11 +09:00
/// var map = db.GetMapping(typeof(Person));
/// var results = db.Query(map, "SELECT * FROM Person WHERE Id = ?", 1);
/// if (results.Count > 0) {
/// var person = results[0] as Person;
/// }
/// </code>
/// </example>
public List < object > Query ( TableMapping map , string query , params object [ ] args )
{
var cmd = CreateCommand ( query , args ) ;
return cmd . ExecuteQuery < object > ( map ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 인수가 있는 명령 텍스트(SQL)로 SQLiteCommand를 생성하고 실행합니다.
/// 명령 텍스트에서 각 인수에 대해 '?'를 배치한 다음 해당 명령을 실행합니다.
/// 지정된 매핑을 사용하여 각 결과 행을 반환합니다.
/// 이 함수는 내성(introspection)을 통해 데이터베이스를 쿼리하기 위해
/// 라이브러리에서만 사용됩니다. 일반적으로 사용되지 않습니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="map">
2025-08-11 18:49:20 +09:00
/// 결과 행을 객체로 변환하는 데 사용할 <see cref="TableMapping"/>.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="query">
2025-08-11 18:49:20 +09:00
/// 완전히 이스케이프된 SQL.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="args">
2025-08-11 18:49:20 +09:00
/// 쿼리에서 '?'의 출현을 대체하는 인수.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 쿼리에 의해 반환된 각 행에 대해 하나의 결과를 갖는 열거 가능.
/// 열거자는 MoveNext를 호출할 때마다 sqlite3_step을 호출하므로,
/// 열거자의 수명 동안 데이터베이스 연결이 열려 있어야 합니다.
2025-06-04 23:10:11 +09:00
/// </returns>
public IEnumerable < object > DeferredQuery ( TableMapping map , string query , params object [ ] args )
{
var cmd = CreateCommand ( query , args ) ;
return cmd . ExecuteDeferredQuery < object > ( map ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 주어진 타입이 나타내는 테이블에 대해 쿼리 가능한 인터페이스를 반환합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <returns>
2025-08-11 18:49:20 +09:00
/// Where, OrderBy, Take 쿼리를 네이티브 SQL로 변환할 수 있는 쿼리 가능한 객체.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // LINQ를 사용하여 데이터 쿼리하기
2025-06-10 01:09:36 +09:00
/// var query = db.Table<Person>()
2025-06-04 23:10:11 +09:00
/// .Where(p => p.Age > 18)
/// .OrderBy(p => p.Name);
///
/// foreach(var adult in query) {
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"성인: {adult.Name}, {adult.Age}세");
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public TableQuery < T > Table < T > ( ) where T : new ( )
{
return new TableQuery < T > ( this ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 기본 키를 가진 객체를 테이블에서 검색합니다.
/// 이 메서드는 지정된 타입에 (PrimaryKeyAttribute를 사용하여)
/// 지정된 PrimaryKey가 있어야 합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="pk">
2025-08-11 18:49:20 +09:00
/// 기본 키.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 주어진 기본 키를 가진 객체.
/// 객체를 찾을 수 없으면 not found 예외를 발생시킵니다.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 기본 키로 Person 객체 가져오기
2025-06-04 23:10:11 +09:00
/// try {
2025-06-10 01:09:36 +09:00
/// var person = db.Get<Person>(1);
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"이름: {person.Name}");
2025-06-04 23:10:11 +09:00
/// } catch (Exception) {
2025-08-11 18:49:20 +09:00
/// Console.WriteLine("해당 ID의 Person을 찾을 수 없습니다");
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public T Get < T > ( object pk ) where T : new ( )
{
var map = GetMapping ( typeof ( T ) ) ;
return Query < T > ( map . GetByPrimaryKeySql , pk ) . First ( ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 테이블에서 조건식과 일치하는 첫 번째 객체를 검색합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="predicate">
2025-08-11 18:49:20 +09:00
/// 어떤 객체를 찾을지 결정하는 조건식.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 주어진 조건식과 일치하는 객체.
/// 객체를 찾을 수 없으면 not found 예외를 발생시킵니다.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 조건식으로 Person 객체 가져오기
2025-06-04 23:10:11 +09:00
/// try {
2025-06-10 01:09:36 +09:00
/// var person = db.Get<Person>(p => p.Email == "test@example.com");
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"이름: {person.Name}");
2025-06-04 23:10:11 +09:00
/// } catch (Exception) {
2025-08-11 18:49:20 +09:00
/// Console.WriteLine("조건에 맞는 Person을 찾을 수 없습니다");
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public T Get < T > ( Expression < Func < T , bool > > predicate ) where T : new ( )
{
return Table < T > ( ) . Where ( predicate ) . First ( ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 테이블에서 지정된 기본 키를 가진 객체를 검색합니다.
/// 이 메서드는 지정된 타입에 (PrimaryKeyAttribute를 사용하여)
/// 지정된 PrimaryKey가 있어야 합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="pk">
2025-08-11 18:49:20 +09:00
/// 기본 키.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 주어진 기본 키를 가진 객체 또는
/// 객체를 찾을 수 없으면 null.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 기본 키로 Person 객체 찾기 (없으면 null 반환)
2025-06-10 01:09:36 +09:00
/// var person = db.Find<Person>(1);
2025-06-04 23:10:11 +09:00
/// if (person != null) {
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"찾았습니다: {person.Name}");
2025-06-04 23:10:11 +09:00
/// } else {
2025-08-11 18:49:20 +09:00
/// Console.WriteLine("해당 ID의 Person이 없습니다");
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public T Find < T > ( object pk ) where T : new ( )
{
var map = GetMapping ( typeof ( T ) ) ;
return Query < T > ( map . GetByPrimaryKeySql , pk ) . FirstOrDefault ( ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 테이블에서 지정된 기본 키를 가진 객체를 검색합니다.
/// 이 메서드는 지정된 타입에 (PrimaryKeyAttribute를 사용하여)
/// 지정된 PrimaryKey가 있어야 합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="pk">
2025-08-11 18:49:20 +09:00
/// 기본 키.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="map">
2025-08-11 18:49:20 +09:00
/// 객체 타입을 식별하는 데 사용되는 TableMapping.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 주어진 기본 키를 가진 객체 또는
/// 객체를 찾을 수 없으면 null.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 매핑 객체를 사용해 객체 찾기
2025-06-04 23:10:11 +09:00
/// var map = db.GetMapping(typeof(Person));
/// var obj = db.Find(1, map);
/// if (obj != null) {
/// var person = obj as Person;
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"찾았습니다: {person.Name}");
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public object Find ( object pk , TableMapping map )
{
return Query ( map , map . GetByPrimaryKeySql , pk ) . FirstOrDefault ( ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 테이블에서 조건식과 일치하는 첫 번째 객체를 검색합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="predicate">
2025-08-11 18:49:20 +09:00
/// 어떤 객체를 찾을지 결정하는 조건식.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 주어진 조건식과 일치하는 객체 또는
/// 객체를 찾을 수 없으면 null.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 조건식으로 Person 객체 찾기 (없으면 null 반환)
2025-06-10 01:09:36 +09:00
/// var person = db.Find<Person>(p => p.Email == "test@example.com");
2025-06-04 23:10:11 +09:00
/// if (person != null) {
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"찾았습니다: {person.Name}");
2025-06-04 23:10:11 +09:00
/// } else {
2025-08-11 18:49:20 +09:00
/// Console.WriteLine("해당 이메일의 Person이 없습니다");
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public T Find < T > ( Expression < Func < T , bool > > predicate ) where T : new ( )
{
return Table < T > ( ) . Where ( predicate ) . FirstOrDefault ( ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// <see cref="BeginTransaction"/>이 호출되었고 데이터베이스가 <see cref="Commit"/>을 기다리고 있는지 여부.
2025-06-04 23:10:11 +09:00
/// </summary>
public bool IsInTransaction
{
get { return _transactionDepth > 0 ; }
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 새 트랜잭션을 시작합니다. 트랜잭션을 종료하려면 <see cref="Commit"/>을 호출하세요.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <example cref="System.InvalidOperationException">트랜잭션이 이미 시작된 경우 예외가 발생합니다.</example>
2025-06-04 23:10:11 +09:00
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 트랜잭션 사용
2025-06-04 23:10:11 +09:00
/// try {
/// db.BeginTransaction();
///
2025-08-11 18:49:20 +09:00
/// // 여러 작업 수행
/// db.Insert(new Person { Name = "홍길동", Age = 30 });
/// db.Insert(new Person { Name = "김철수", Age = 25 });
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 성공적으로 완료되면 트랜잭션 커밋
2025-06-04 23:10:11 +09:00
/// db.Commit();
/// } catch (Exception) {
2025-08-11 18:49:20 +09:00
/// // 오류 발생시 자동으로 롤백됩니다
/// Console.WriteLine("트랜잭션 실패");
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public void BeginTransaction ( )
{
2025-08-11 18:49:20 +09:00
// BEGIN 명령은 트랜잭션 스택이 비어 있는 경우, 즉 보류 중인 트랜잭션이 없는 경우에만 작동합니다.
// BEGIN 명령이 호출될 때 트랜잭션 스택이 비어 있지 않으면 명령이 오류와 함께 실패합니다.
// 오류로 인해 충돌이 발생하는 대신, 오류가 발생할 수 있는 BeginTransaction 호출을 무시합니다.
2025-06-04 23:10:11 +09:00
if ( Interlocked . CompareExchange ( ref _transactionDepth , 1 , 0 ) = = 0 )
{
try
{
Execute ( "begin transaction" ) ;
}
catch ( Exception ex )
{
var sqlExp = ex as SQLiteException ;
if ( sqlExp ! = null )
{
2025-08-11 18:49:20 +09:00
// 아래 나열된 오류에 대해서는 애플리케이션에서 ROLLBACK 명령을 명시적으로 실행하여 대응하는 것이 좋습니다.
// TODO: 이 롤백 안전 장치는 모든 오류 발생 사이트에 적용되어야 합니다.
2025-06-04 23:10:11 +09:00
switch ( sqlExp . Result )
{
case SQLite3 . Result . IOError :
case SQLite3 . Result . Full :
case SQLite3 . Result . Busy :
case SQLite3 . Result . NoMem :
case SQLite3 . Result . Interrupt :
RollbackTo ( null , true ) ;
break ;
}
}
else
{
2025-08-11 18:49:20 +09:00
// SaveTransactionPoint에서 catch 이후 이미 트랜잭션 포인트를
// 생성한 경우에는 VolatileWrite가 아닌 decrement를 호출하세요.
2025-06-04 23:10:11 +09:00
Interlocked . Decrement ( ref _transactionDepth ) ;
}
throw ;
}
}
else
{
2025-08-11 18:49:20 +09:00
// 이미 열려 있는 트랜잭션에 BeginTransaction을 호출하는 것은 유효하지 않습니다.
2025-06-04 23:10:11 +09:00
throw new InvalidOperationException ( "Cannot begin a transaction while already in a transaction." ) ;
}
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 현재 트랜잭션 타임라인의 지점에서 데이터베이스에 세이브포인트를 생성합니다.
/// 진행 중인 트랜잭션이 없으면 새 트랜잭션을 시작합니다.
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// 반환된 세이브포인트 이후의 작업을 취소하려면 <see cref="RollbackTo"/>를 호출하세요.
/// 반환된 세이브포인트 이후의 작업을 확정하려면 <see cref="Release"/>를 호출하세요.
/// 트랜잭션을 종료하고 모든 변경사항을 확정하려면 <see cref="Commit"/>을 호출하세요.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <returns>세이브포인트를 식별하는 문자열.</returns>
2025-06-04 23:10:11 +09:00
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 세이브포인트를 사용한 트랜잭션 처리
2025-06-04 23:10:11 +09:00
/// db.BeginTransaction();
///
2025-08-11 18:49:20 +09:00
/// // 첫 번째 작업 수행
/// db.Insert(new Person { Name = "홍길동", Age = 30 });
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 세이브포인트 생성
2025-06-04 23:10:11 +09:00
/// string savepoint = db.SaveTransactionPoint();
///
/// try {
2025-08-11 18:49:20 +09:00
/// // 두 번째 작업 수행
/// db.Insert(new Person { Name = "김철수", Age = 25 });
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 문제가 없으면 세이브포인트 해제(확정)
2025-06-04 23:10:11 +09:00
/// db.Release(savepoint);
/// } catch(Exception) {
2025-08-11 18:49:20 +09:00
/// // 문제가 발생하면 세이브포인트로 롤백
2025-06-04 23:10:11 +09:00
/// db.RollbackTo(savepoint);
/// }
///
2025-08-11 18:49:20 +09:00
/// // 트랜잭션 확정
2025-06-04 23:10:11 +09:00
/// db.Commit();
/// </code>
/// </example>
public string SaveTransactionPoint ( )
{
int depth = Interlocked . Increment ( ref _transactionDepth ) - 1 ;
string retVal = "S" + _rand . Next ( short . MaxValue ) + "D" + depth ;
try
{
Execute ( "savepoint " + retVal ) ;
}
catch ( Exception ex )
{
var sqlExp = ex as SQLiteException ;
if ( sqlExp ! = null )
{
// It is recommended that applications respond to the errors listed below
// by explicitly issuing a ROLLBACK command.
// TODO: This rollback failsafe should be localized to all throw sites.
switch ( sqlExp . Result )
{
case SQLite3 . Result . IOError :
case SQLite3 . Result . Full :
case SQLite3 . Result . Busy :
case SQLite3 . Result . NoMem :
case SQLite3 . Result . Interrupt :
RollbackTo ( null , true ) ;
break ;
}
}
else
{
Interlocked . Decrement ( ref _transactionDepth ) ;
}
throw ;
}
return retVal ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// <see cref="BeginTransaction"/> 또는 <see cref="SaveTransactionPoint"/>로 시작된 트랜잭션을 롤백합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 트랜잭션을 시작하고 실패 시 롤백
2025-06-04 23:10:11 +09:00
/// try {
/// db.BeginTransaction();
2025-08-11 18:49:20 +09:00
/// db.Insert(new Person { Name = "홍길동", Age = 30 });
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 오류 발생할 수 있는 작업
2025-06-04 23:10:11 +09:00
/// if (SomeConditionFails()) {
2025-08-11 18:49:20 +09:00
/// // 트랜잭션 롤백
2025-06-04 23:10:11 +09:00
/// db.Rollback();
/// return;
/// }
///
2025-08-11 18:49:20 +09:00
/// // 성공 시 커밋
2025-06-04 23:10:11 +09:00
/// db.Commit();
/// } catch(Exception) {
2025-08-11 18:49:20 +09:00
/// // 예외 발생 시 롤백
2025-06-04 23:10:11 +09:00
/// db.Rollback();
/// }
/// </code>
/// </example>
public void Rollback ( )
{
RollbackTo ( null , false ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// <see cref="BeginTransaction"/> 또는 SaveTransactionPoint에 의해 생성된 세이브포인트로 롤백합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <param name="savepoint"><see cref="SaveTransactionPoint"/>에 의해 반환된 롤백할 세이브포인트의 이름. savepoint가 null이거나 비어 있으면 이 메서드는 <see cref="Rollback"/> 호출과 동일합니다.</param>
2025-06-04 23:10:11 +09:00
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 세이브포인트를 사용한 부분 롤백
2025-06-04 23:10:11 +09:00
/// db.BeginTransaction();
///
2025-08-11 18:49:20 +09:00
/// // 첫 번째 작업 수행
/// db.Insert(new Person { Name = "홍길동", Age = 30 });
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 세이브포인트 생성
2025-06-04 23:10:11 +09:00
/// string savepoint = db.SaveTransactionPoint();
///
2025-08-11 18:49:20 +09:00
/// // 두 번째 작업 수행
/// db.Insert(new Person { Name = "김철수", Age = 25 });
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 두 번째 작업만 롤백 (세이브포인트로 되돌리기)
2025-06-04 23:10:11 +09:00
/// db.RollbackTo(savepoint);
///
2025-08-11 18:49:20 +09:00
/// // 첫 번째 작업은 여전히 유효하며 트랜잭션을 커밋
2025-06-04 23:10:11 +09:00
/// db.Commit();
/// </code>
/// </example>
public void RollbackTo ( string savepoint )
{
RollbackTo ( savepoint , false ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// <see cref="BeginTransaction"/>에 의해 시작된 거래를 롤백합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <param name="noThrow">예외 발생을 방지하려면 true, 그렇지 않으면 false</param>
2025-06-04 23:10:11 +09:00
void RollbackTo ( string savepoint , bool noThrow )
{
// Rolling back without a TO clause rolls backs all transactions
// and leaves the transaction stack empty.
try
{
if ( String . IsNullOrEmpty ( savepoint ) )
{
if ( Interlocked . Exchange ( ref _transactionDepth , 0 ) > 0 )
{
Execute ( "rollback" ) ;
}
}
else
{
DoSavePointExecute ( savepoint , "rollback to " ) ;
}
}
catch ( SQLiteException )
{
if ( ! noThrow )
throw ;
}
// No need to rollback if there are no transactions open.
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// <see cref="SaveTransactionPoint"/>에서 반환된 세이브포인트를 해제합니다.
/// 세이브포인트를 해제하면 해당 세이브포인트가 트랜잭션을 시작했던 경우
/// 그 세이브포인트 이후의 변경사항이 영구적으로 적용됩니다.
/// 그렇지 않은 경우에는 <see cref="Commit"/> 호출이 있을 때까지 변경사항이 대기 상태로 유지됩니다.
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// RELEASE 명령은 SAVEPOINT에 대한 COMMIT과 같습니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <param name="savepoint">해제할 세이브포인트의 이름. 이 문자열은 <see cref="SaveTransactionPoint"/> 호출 결과여야 합니다.</param>
2025-06-04 23:10:11 +09:00
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 세이브포인트 해제(확정) 사용 예제
2025-06-04 23:10:11 +09:00
/// db.BeginTransaction();
///
2025-08-11 18:49:20 +09:00
/// // 작업 수행
/// db.Insert(new Person { Name = "홍길동", Age = 30 });
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 세이브포인트 생성
2025-06-04 23:10:11 +09:00
/// string savepoint1 = db.SaveTransactionPoint();
2025-08-11 18:49:20 +09:00
/// db.Insert(new Person { Name = "김철수", Age = 25 });
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 다른 세이브포인트 생성
2025-06-04 23:10:11 +09:00
/// string savepoint2 = db.SaveTransactionPoint();
2025-08-11 18:49:20 +09:00
/// db.Insert(new Person { Name = "이영희", Age = 35 });
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 두 번째 세이브포인트의 변경사항을 확정
2025-06-04 23:10:11 +09:00
/// db.Release(savepoint2);
///
2025-08-11 18:49:20 +09:00
/// // 이제 첫 번째 세이브포인트의 변경사항도 확정
2025-06-04 23:10:11 +09:00
/// db.Release(savepoint1);
///
2025-08-11 18:49:20 +09:00
/// // 전체 트랜잭션 커밋
2025-06-04 23:10:11 +09:00
/// db.Commit();
/// </code>
/// </example>
public void Release ( string savepoint )
{
DoSavePointExecute ( savepoint , "release " ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 세이브포인트에 대한 명령을 실행합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <param name="savepoint">세이브포인트 식별자</param>
/// <param name="cmd">실행할 SQL 명령(rollback to 또는 release)</param>
/// <exception cref="ArgumentException">세이브포인트가 유효하지 않은 경우 발생</exception>
2025-06-04 23:10:11 +09:00
void DoSavePointExecute ( string savepoint , string cmd )
{
// Validate the savepoint
int firstLen = savepoint . IndexOf ( 'D' ) ;
if ( firstLen > = 2 & & savepoint . Length > firstLen + 1 )
{
int depth ;
if ( Int32 . TryParse ( savepoint . Substring ( firstLen + 1 ) , out depth ) )
{
// TODO: Mild race here, but inescapable without locking almost everywhere.
if ( 0 < = depth & & depth < _transactionDepth )
{
#if NETFX_CORE
Volatile . Write ( ref _transactionDepth , depth ) ;
#elif SILVERLIGHT
_transactionDepth = depth ;
#else
Thread . VolatileWrite ( ref _transactionDepth , depth ) ;
#endif
Execute ( cmd + savepoint ) ;
return ;
}
}
}
throw new ArgumentException ( "savePoint is not valid, and should be the result of a call to SaveTransactionPoint." , "savePoint" ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// <see cref="BeginTransaction"/>으로 시작된 트랜잭션을 커밋합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 트랜잭션 커밋 예제
2025-06-04 23:10:11 +09:00
/// try {
/// db.BeginTransaction();
///
2025-08-11 18:49:20 +09:00
/// // 여러 작업 수행
/// db.Insert(new Person { Name = "홍길동", Age = 30 });
/// db.Insert(new Person { Name = "김철수", Age = 25 });
/// db.Insert(new Person { Name = "이영희", Age = 35 });
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 모든 작업이 성공적으로 완료되면 트랜잭션 커밋
2025-06-04 23:10:11 +09:00
/// db.Commit();
/// } catch(Exception ex) {
2025-08-11 18:49:20 +09:00
/// // 오류 발생 시 롤백
2025-06-04 23:10:11 +09:00
/// db.Rollback();
2025-08-11 18:49:20 +09:00
/// Console.WriteLine("트랜잭션 실패: " + ex.Message);
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public void Commit ( )
{
if ( Interlocked . Exchange ( ref _transactionDepth , 0 ) ! = 0 )
{
Execute ( "commit" ) ;
}
// Do nothing on a commit with no open transaction
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 주어진 액션을 SAVEPOINT로 감싸서 (중첩될 수 있는) 트랜잭션 내에서 실행합니다.
/// 예외가 발생하면 현재 세이브포인트뿐만 아니라 전체 트랜잭션이 롤백됩니다.
/// 발생한 예외는 다시 던져집니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="action">
2025-08-11 18:49:20 +09:00
/// 트랜잭션 내에서 수행할 <see cref="Action"/>. action은 연결에 대한 여러 작업을 포함할 수 있지만
/// <see cref="BeginTransaction"/> 또는 <see cref="Commit"/>을 호출해서는 안 됩니다.
2025-06-04 23:10:11 +09:00
/// </param>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // RunInTransaction을 사용한 트랜잭션 처리
2025-06-04 23:10:11 +09:00
/// db.RunInTransaction(() => {
2025-08-11 18:49:20 +09:00
/// // 모든 작업은 하나의 트랜잭션으로 실행됩니다.
/// db.Insert(new Person { Name = "홍길동", Age = 30 });
/// db.Insert(new Person { Name = "김철수", Age = 25 });
/// db.Update(new Person { Id = 1, Name = "홍길동", Age = 31 });
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 이 블록 내에서 예외가 발생하면 모든 작업이 롤백됩니다.
2025-06-04 23:10:11 +09:00
/// });
///
2025-08-11 18:49:20 +09:00
/// // 여기에 도달하면 트랜잭션이 성공적으로 커밋된 것입니다.
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public void RunInTransaction ( Action action )
{
try
{
lock ( syncObjects [ DatabasePath ] )
{
var savePoint = SaveTransactionPoint ( ) ;
action ( ) ;
Release ( savePoint ) ;
}
}
catch ( Exception )
{
Rollback ( ) ;
throw ;
}
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 다른 스레드가 동일한 데이터베이스에 접근하지 못하도록 차단하면서 주어진 액션을 실행합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="action">
2025-08-11 18:49:20 +09:00
/// 락(lock) 내에서 수행할 <see cref="Action"/>.
2025-06-04 23:10:11 +09:00
/// </param>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // RunInDatabaseLock을 사용한 스레드 안전한 데이터베이스 접근
2025-06-04 23:10:11 +09:00
/// db.RunInDatabaseLock(() => {
2025-08-11 18:49:20 +09:00
/// // 이 블록 내의 작업은 다른 스레드에서 동일한 데이터베이스에 접근할 수 없습니다.
2025-06-10 01:09:36 +09:00
/// var count = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
2025-06-04 23:10:11 +09:00
///
/// if (count > 0) {
2025-06-10 01:09:36 +09:00
/// var people = db.Query<Person>("SELECT * FROM Person");
2025-06-04 23:10:11 +09:00
/// foreach (var person in people) {
2025-08-11 18:49:20 +09:00
/// // 안전하게 데이터 처리
2025-06-04 23:10:11 +09:00
/// ProcessPerson(person);
/// }
/// }
/// });
/// </code>
/// </example>
public void RunInDatabaseLock ( Action action )
{
lock ( syncObjects [ DatabasePath ] )
{
action ( ) ;
}
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 모든 객체를 삽입합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="objects">
2025-08-11 18:49:20 +09:00
/// 삽입할 객체의 <see cref="IEnumerable"/> 컬렉션.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 테이블에 추가된 행의 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 객체 목록을 한 번에 삽입
2025-06-10 01:09:36 +09:00
/// var people = new List<Person>
2025-06-04 23:10:11 +09:00
/// {
2025-08-11 18:49:20 +09:00
/// new Person { Name = "홍길동", Age = 30 },
/// new Person { Name = "김철수", Age = 25 },
/// new Person { Name = "이영희", Age = 35 }
2025-06-04 23:10:11 +09:00
/// };
///
2025-08-11 18:49:20 +09:00
/// // 모든 객체를 삽입하고 영향받은 행 수를 반환
2025-06-04 23:10:11 +09:00
/// int count = db.InsertAll(people);
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"삽입된 행 수: {count}");
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public int InsertAll ( System . Collections . IEnumerable objects )
{
var c = 0 ;
RunInTransaction ( ( ) = > {
foreach ( var r in objects )
{
c + = Insert ( r ) ;
}
} ) ;
return c ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 모든 객체를 삽입합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="objects">
2025-08-11 18:49:20 +09:00
/// 삽입할 객체들의 <see cref="IEnumerable"/> 컬렉션.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="extra">
2025-08-11 18:49:20 +09:00
/// 명령에 삽입되는 리터럴 SQL 코드입니다. INSERT {extra} INTO ...
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 테이블에 추가된 행의 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 여러 객체 한 번에 삽입 (충돌 무시 옵션 사용)
2025-06-10 01:09:36 +09:00
/// var people = new List<Person>
2025-06-04 23:10:11 +09:00
/// {
2025-08-11 18:49:20 +09:00
/// new Person { Id = 1, Name = "홍길동", Age = 30 },
/// new Person { Id = 2, Name = "김철수", Age = 25 },
/// new Person { Id = 3, Name = "이영희", Age = 35 }
2025-06-04 23:10:11 +09:00
/// };
///
2025-08-11 18:49:20 +09:00
/// // OR IGNORE 옵션으로 삽입하여 중복 키 충돌 무시
2025-06-04 23:10:11 +09:00
/// int count = db.InsertAll(people, "OR IGNORE");
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"삽입된 행 수: {count}");
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public int InsertAll ( System . Collections . IEnumerable objects , string extra )
{
var c = 0 ;
RunInTransaction ( ( ) = > {
foreach ( var r in objects )
{
c + = Insert ( r , extra ) ;
}
} ) ;
return c ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 모든 객체를 삽입합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="objects">
2025-08-11 18:49:20 +09:00
/// 삽입할 객체들의 <see cref="IEnumerable"/> 컬렉션.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="objType">
2025-08-11 18:49:20 +09:00
/// 삽입할 객체들의 타입.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 테이블에 추가된 행의 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 동적 타입으로 여러 객체 한 번에 삽입
2025-06-10 01:09:36 +09:00
/// var people = new List<object>
2025-06-04 23:10:11 +09:00
/// {
2025-08-11 18:49:20 +09:00
/// new Person { Name = "홍길동", Age = 30 },
/// new Person { Name = "김철수", Age = 25 }
2025-06-04 23:10:11 +09:00
/// };
///
2025-08-11 18:49:20 +09:00
/// // 특정 타입으로 지정하여 삽입
2025-06-04 23:10:11 +09:00
/// int count = db.InsertAll(people, typeof(Person));
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"삽입된 행 수: {count}");
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public int InsertAll ( System . Collections . IEnumerable objects , Type objType )
{
var c = 0 ;
RunInTransaction ( ( ) = > {
foreach ( var r in objects )
{
c + = Insert ( r , objType ) ;
}
} ) ;
return c ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 주어진 객체를 삽입하고, 자동 증가 기본 키가 있다면 그 값을 가져옵니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="obj">
2025-08-11 18:49:20 +09:00
/// 삽입할 객체.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 테이블에 추가된 행의 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // Person 객체를 생성하고 삽입
/// var person = new Person { Name = "홍길동", Age = 30 };
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 객체 삽입 및 영향받은 행 수 확인
2025-06-04 23:10:11 +09:00
/// int rowsAffected = db.Insert(person);
///
2025-08-11 18:49:20 +09:00
/// // 자동 증가 ID가 있는 경우, 삽입 후 ID가 객체에 설정됨
/// Console.WriteLine($"삽입된 ID: {person.Id}");
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public int Insert ( object obj )
{
if ( obj = = null )
{
return 0 ;
}
return Insert ( obj , "" , obj . GetType ( ) ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 주어진 객체를 삽입하고, 자동 증가 기본 키가 있다면 그 값을 가져옵니다.
/// UNIQUE 제약 조건 위반이 발생한 경우 기존 객체를 삭제합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="obj">
2025-08-11 18:49:20 +09:00
/// 삽입할 객체.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 수정된 행의 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 중복 키가 있는 경우 교체하여 삽입
/// var existingPerson = new Person { Id = 1, Name = "홍길동", Age = 30 };
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 동일한 Id로 새 객체 삽입 (기존 객체는 교체됨)
/// db.InsertOrReplace(new Person { Id = 1, Name = "홍길동(수정)", Age = 31 });
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 값이 변경된 객체를 교체 삽입하는 방식으로 업데이트할 수도 있음
2025-06-10 01:09:36 +09:00
/// var person = db.Get<Person>(1);
2025-06-04 23:10:11 +09:00
/// person.Age += 1;
/// db.InsertOrReplace(person);
/// </code>
/// </example>
public int InsertOrReplace ( object obj )
{
if ( obj = = null )
{
return 0 ;
}
return Insert ( obj , "OR REPLACE" , obj . GetType ( ) ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 주어진 객체를 삽입하고 자동 증가 기본 키가 있다면 그 값을 가져옵니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="obj">
2025-08-11 18:49:20 +09:00
/// 삽입할 객체.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="extra">
2025-08-11 18:49:20 +09:00
/// 명령에 삽입되는 리터럴 SQL 코드입니다. INSERT {extra} INTO ...
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="objType">
2025-08-11 18:49:20 +09:00
/// 삽입할 객체의 타입.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 테이블에 추가된 행의 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 특정 타입으로 객체 삽입
/// object person = new Person { Name = "홍길동", Age = 30 };
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // OR IGNORE 옵션을 사용하여 충돌 시 무시
2025-06-04 23:10:11 +09:00
/// int count = db.Insert(person, "OR IGNORE", typeof(Person));
///
2025-08-11 18:49:20 +09:00
/// // 또는 다른 SQL 옵션 추가
2025-06-04 23:10:11 +09:00
/// db.Insert(person, "OR FAIL", typeof(Person));
/// </code>
/// </example>
public int Insert ( object obj , Type objType )
{
return Insert ( obj , "" , objType ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 주어진 객체를 삽입하고 자동 증가 기본 키가 있다면 그 값을 가져옵니다.
/// UNIQUE 제약 조건 위반이 발생한 경우 기존 객체를 교체합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="obj">
2025-08-11 18:49:20 +09:00
/// 삽입할 객체.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="objType">
2025-08-11 18:49:20 +09:00
/// 삽입할 객체의 타입.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 수정된 행의 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 특정 타입으로 객체 삽입 및 교체
/// object person = new Person { Id = 1, Name = "홍길동", Age = 30 };
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 교체 삽입 실행
2025-06-04 23:10:11 +09:00
/// int count = db.InsertOrReplace(person, typeof(Person));
/// </code>
/// </example>
public int InsertOrReplace ( object obj , Type objType )
{
return Insert ( obj , "OR REPLACE" , objType ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 주어진 객체를 삽입하고 자동 증가 기본 키가 있다면 그 값을 가져옵니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="obj">
2025-08-11 18:49:20 +09:00
/// 삽입할 객체.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="extra">
2025-08-11 18:49:20 +09:00
/// 명령에 삽입되는 리터럴 SQL 코드입니다. INSERT {extra} INTO ...
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 테이블에 추가된 행의 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 충돌 무시 옵션으로 객체 삽입
/// var person = new Person { Id = 1, Name = "홍길동", Age = 30 };
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // OR IGNORE 옵션으로 삽입하여 중복 키 충돌 무시
2025-06-04 23:10:11 +09:00
/// int count = db.Insert(person, "OR IGNORE");
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"삽입된 행 수: {count}");
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public int Insert ( object obj , string extra )
{
if ( obj = = null )
{
return 0 ;
}
return Insert ( obj , extra , obj . GetType ( ) ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 주어진 객체를 삽입하고 자동 증가 기본 키가 있다면 그 값을 가져옵니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="obj">
2025-08-11 18:49:20 +09:00
/// 삽입할 객체.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="extra">
2025-08-11 18:49:20 +09:00
/// 명령에 삽입되는 리터럴 SQL 코드입니다. INSERT {extra} INTO ...
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="objType">
2025-08-11 18:49:20 +09:00
/// 삽입할 객체의 타입.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 테이블에 추가된 행의 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 다양한 옵션으로 객체 삽입
/// var person = new Person { Name = "홍길동", Age = 30 };
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 기본 삽입
2025-06-04 23:10:11 +09:00
/// db.Insert(person);
///
2025-08-11 18:49:20 +09:00
/// // OR IGNORE 옵션으로 충돌 시 무시
2025-06-04 23:10:11 +09:00
/// db.Insert(person, "OR IGNORE");
///
2025-08-11 18:49:20 +09:00
/// // 특정 타입으로 지정
2025-06-04 23:10:11 +09:00
/// db.Insert(person, "OR REPLACE", typeof(Person));
/// </code>
/// </example>
public int Insert ( object obj , string extra , Type objType )
{
if ( obj = = null | | objType = = null )
{
return 0 ;
}
var map = GetMapping ( objType ) ;
#if NETFX_CORE
if ( map . PK ! = null & & map . PK . IsAutoGuid )
{
// no GetProperty so search our way up the inheritance chain till we find it
PropertyInfo prop ;
while ( objType ! = null )
{
var info = objType . GetTypeInfo ( ) ;
prop = info . GetDeclaredProperty ( map . PK . PropertyName ) ;
if ( prop ! = null )
{
if ( prop . GetValue ( obj , null ) . Equals ( Guid . Empty ) )
{
prop . SetValue ( obj , Guid . NewGuid ( ) , null ) ;
}
break ;
}
objType = info . BaseType ;
}
}
#else
if ( map . PK ! = null & & map . PK . IsAutoGuid )
{
var prop = objType . GetProperty ( map . PK . PropertyName ) ;
if ( prop ! = null )
{
//if (prop.GetValue(obj, null).Equals(Guid.Empty)) {
if ( prop . GetGetMethod ( ) . Invoke ( obj , null ) . Equals ( Guid . Empty ) )
{
prop . SetValue ( obj , Guid . NewGuid ( ) , null ) ;
}
}
}
#endif
var replacing = string . Compare ( extra , "OR REPLACE" , StringComparison . OrdinalIgnoreCase ) = = 0 ;
var cols = replacing ? map . InsertOrReplaceColumns : map . InsertColumns ;
var vals = new object [ cols . Length ] ;
for ( var i = 0 ; i < vals . Length ; i + + )
{
vals [ i ] = cols [ i ] . GetValue ( obj ) ;
}
var insertCmd = map . GetInsertCommand ( this , extra ) ;
int count ;
try
{
count = insertCmd . ExecuteNonQuery ( vals ) ;
}
catch ( SQLiteException ex )
{
if ( SQLite3 . ExtendedErrCode ( this . Handle ) = = SQLite3 . ExtendedResult . ConstraintNotNull )
{
throw NotNullConstraintViolationException . New ( ex . Result , ex . Message , map , obj ) ;
}
throw ;
}
if ( map . HasAutoIncPK )
{
var id = SQLite3 . LastInsertRowid ( Handle ) ;
map . SetAutoIncPK ( obj , id ) ;
}
return count ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 객체를 사용하여 테이블의 모든 열을 기본 키를 제외하고 업데이트합니다.
/// 객체에는 기본 키가 있어야 합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="obj">
2025-08-11 18:49:20 +09:00
/// 업데이트할 객체. PrimaryKeyAttribute를 사용하여 지정된 기본 키가 있어야 합니다.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 업데이트된 행의 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 데이터베이스에서 객체 검색
2025-06-10 01:09:36 +09:00
/// var person = db.Get<Person>(1);
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 객체 속성 변경
/// person.Name = "홍길동(수정됨)";
2025-06-04 23:10:11 +09:00
/// person.Age = 31;
///
2025-08-11 18:49:20 +09:00
/// // 변경된 객체 업데이트
2025-06-04 23:10:11 +09:00
/// int rowsAffected = db.Update(person);
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"업데이트된 행 수: {rowsAffected}");
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public int Update ( object obj )
{
if ( obj = = null )
{
return 0 ;
}
return Update ( obj , obj . GetType ( ) ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 객체를 사용하여 테이블의 모든 열을 기본 키를 제외하고 업데이트합니다.
/// 객체에는 기본 키가 있어야 합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="obj">
2025-08-11 18:49:20 +09:00
/// 업데이트할 객체. PrimaryKeyAttribute를 사용하여 지정된 기본 키가 있어야 합니다.
2025-06-04 23:10:11 +09:00
/// </param>
/// <param name="objType">
2025-08-11 18:49:20 +09:00
/// 업데이트할 객체의 타입.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 업데이트된 행의 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 동적 타입으로 객체 업데이트
/// object person = new Person { Id = 1, Name = "홍길동(수정됨)", Age = 31 };
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 특정 타입으로 지정하여 업데이트
2025-06-04 23:10:11 +09:00
/// int count = db.Update(person, typeof(Person));
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"업데이트된 행 수: {count}");
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public int Update ( object obj , Type objType )
{
int rowsAffected = 0 ;
if ( obj = = null | | objType = = null )
{
return 0 ;
}
var map = GetMapping ( objType ) ;
var pk = map . PK ;
if ( pk = = null )
{
throw new NotSupportedException ( "Cannot update " + map . TableName + ": it has no PK" ) ;
}
var cols = from p in map . Columns
where p ! = pk
select p ;
var vals = from c in cols
select c . GetValue ( obj ) ;
var ps = new List < object > ( vals ) ;
ps . Add ( pk . GetValue ( obj ) ) ;
var q = string . Format ( "update \"{0}\" set {1} where {2} = ? " , map . TableName , string . Join ( "," , ( from c in cols
select "\"" + c . Name + "\" = ? " ) . ToArray ( ) ) , pk . Name ) ;
try
{
rowsAffected = Execute ( q , ps . ToArray ( ) ) ;
}
catch ( SQLiteException ex )
{
if ( ex . Result = = SQLite3 . Result . Constraint & & SQLite3 . ExtendedErrCode ( this . Handle ) = = SQLite3 . ExtendedResult . ConstraintNotNull )
{
throw NotNullConstraintViolationException . New ( ex , map , obj ) ;
}
throw ex ;
}
return rowsAffected ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 모든 객체를 업데이트합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="objects">
2025-08-11 18:49:20 +09:00
/// 업데이트할 객체들의 <see cref="IEnumerable"/> 컬렉션.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 수정된 행의 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 여러 객체 한 번에 업데이트
2025-06-10 01:09:36 +09:00
/// var people = db.Query<Person>("SELECT * FROM Person WHERE Age < 30");
2025-06-04 23:10:11 +09:00
/// foreach (var person in people) {
/// person.Age++;
/// }
///
2025-08-11 18:49:20 +09:00
/// // 모든 수정된 객체를 한 번에 업데이트
2025-06-04 23:10:11 +09:00
/// int count = db.UpdateAll(people);
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"업데이트된 행 수: {count}");
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public int UpdateAll ( System . Collections . IEnumerable objects )
{
var c = 0 ;
RunInTransaction ( ( ) = > {
foreach ( var r in objects )
{
c + = Update ( r ) ;
}
} ) ;
return c ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 기본 키를 사용하여 데이터베이스에서 지정된 객체를 삭제합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="objectToDelete">
2025-08-11 18:49:20 +09:00
/// 삭제할 객체. PrimaryKeyAttribute를 사용하여 지정된 기본 키가 있어야 합니다.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 삭제된 행의 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 데이터베이스에서 객체 검색
2025-06-10 01:09:36 +09:00
/// var person = db.Get<Person>(1);
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 객체 삭제
2025-06-04 23:10:11 +09:00
/// int rowsAffected = db.Delete(person);
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"삭제된 행 수: {rowsAffected}");
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 또는 직접 ID로 삭제
2025-06-10 01:09:36 +09:00
/// rowsAffected = db.Delete<Person>(2);
2025-06-04 23:10:11 +09:00
/// </code>
/// </example>
public int Delete ( object objectToDelete )
{
var map = GetMapping ( objectToDelete . GetType ( ) ) ;
var pk = map . PK ;
if ( pk = = null )
{
throw new NotSupportedException ( "Cannot delete " + map . TableName + ": it has no PK" ) ;
}
var q = string . Format ( "delete from \"{0}\" where \"{1}\" = ?" , map . TableName , pk . Name ) ;
return Execute ( q , pk . GetValue ( objectToDelete ) ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 기본 키를 가진 객체를 삭제합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="primaryKey">
2025-08-11 18:49:20 +09:00
/// 삭제할 객체의 기본 키.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 삭제된 객체의 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <typeparam name='T'>
2025-08-11 18:49:20 +09:00
/// 객체의 타입.
2025-06-04 23:10:11 +09:00
/// </typeparam>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 기본 키로 직접 객체 삭제
2025-06-10 01:09:36 +09:00
/// int rowsDeleted = db.Delete<Person>(1);
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"ID가 1인 Person 객체 {rowsDeleted}개 삭제됨");
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 여러 ID를 차례로 삭제
2025-06-04 23:10:11 +09:00
/// int[] idsToDelete = { 2, 3, 4 };
2025-09-26 18:08:07 +09:00
/// foreach (int Id in idsToDelete) {
/// db.Delete<Person>(Id);
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public int Delete < T > ( object primaryKey )
{
var map = GetMapping ( typeof ( T ) ) ;
var pk = map . PK ;
if ( pk = = null )
{
throw new NotSupportedException ( "Cannot delete " + map . TableName + ": it has no PK" ) ;
}
var q = string . Format ( "delete from \"{0}\" where \"{1}\" = ?" , map . TableName , pk . Name ) ;
return Execute ( q , primaryKey ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 테이블의 모든 객체를 삭제합니다.
/// 주의: 반복하여 강조하자면, 이 메소드는 지정된 테이블의 모든 객체를 삭제합니다.
/// 정말 이 작업을 수행하려는 것이 맞는지 확인하세요.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 삭제된 객체의 수.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <typeparam name='T'>
2025-08-11 18:49:20 +09:00
/// 삭제할 객체의 타입.
2025-06-04 23:10:11 +09:00
/// </typeparam>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 테이블의 모든 데이터 삭제 (주의해서 사용)
2025-06-04 23:10:11 +09:00
/// db.BeginTransaction();
/// try {
2025-08-11 18:49:20 +09:00
/// // 삭제 전 확인을 위한 카운트
2025-06-10 01:09:36 +09:00
/// int beforeCount = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"삭제 전 데이터 수: {beforeCount}");
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 모든 Person 데이터 삭제
2025-06-10 01:09:36 +09:00
/// int deleted = db.DeleteAll<Person>();
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"삭제된 데이터 수: {deleted}");
2025-06-04 23:10:11 +09:00
/// db.Commit();
/// } catch(Exception) {
/// db.Rollback();
2025-08-11 18:49:20 +09:00
/// Console.WriteLine("삭제 작업 취소됨");
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public int DeleteAll < T > ( )
{
var map = GetMapping ( typeof ( T ) ) ;
var query = string . Format ( "delete from \"{0}\"" , map . TableName ) ;
return Execute ( query ) ;
}
~ SQLiteConnection ( )
{
Dispose ( false ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 데이터베이스 연결을 정리하고 닫습니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // using 문을 사용한 자동 리소스 정리
2025-06-04 23:10:11 +09:00
/// using (var db = new SQLiteConnection("database.db"))
/// {
2025-08-11 18:49:20 +09:00
/// // 데이터베이스 작업 수행
2025-06-10 01:09:36 +09:00
/// var people = db.Query<Person>("SELECT * FROM Person");
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // using 블록이 끝나면 자동으로 db.Dispose() 호출됨
2025-06-04 23:10:11 +09:00
/// }
///
2025-08-11 18:49:20 +09:00
/// // 수동으로 리소스 정리
2025-06-04 23:10:11 +09:00
/// var connection = new SQLiteConnection("database.db");
/// try
/// {
2025-08-11 18:49:20 +09:00
/// // 작업 수행
2025-06-10 01:09:36 +09:00
/// connection.CreateTable<Person>();
2025-06-04 23:10:11 +09:00
/// }
/// finally
/// {
/// connection.Dispose();
/// }
/// </code>
/// </example>
public void Dispose ( )
{
Dispose ( true ) ;
GC . SuppressFinalize ( this ) ;
}
protected virtual void Dispose ( bool disposing )
{
Close ( ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 데이터베이스 연결을 닫습니다. 이 메소드는 Dispose에서도 호출됩니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 데이터베이스 연결 닫기
2025-06-04 23:10:11 +09:00
/// db.Close();
///
2025-08-11 18:49:20 +09:00
/// // 또는 using 문을 사용하여 자동으로 닫기
2025-06-04 23:10:11 +09:00
/// using (var db = new SQLiteConnection("database.db")) {
2025-08-11 18:49:20 +09:00
/// // 데이터베이스 작업 수행
2025-06-10 01:09:36 +09:00
/// var count = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"Person 테이블의 행 수: {count}");
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // using 블록이 끝나면 자동으로 db.Dispose()가 호출되어 연결이 닫힘
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public void Close ( )
{
if ( _open & & Handle ! = NullHandle )
{
try
{
if ( _mappings ! = null )
{
foreach ( var sqlInsertCommand in _mappings . Values )
{
sqlInsertCommand . Dispose ( ) ;
}
}
var r = SQLite3 . Close ( Handle ) ;
if ( r ! = SQLite3 . Result . OK )
{
string msg = SQLite3 . GetErrmsg ( Handle ) ;
throw SQLiteException . New ( r , msg ) ;
}
}
finally
{
Handle = NullHandle ;
_open = false ;
}
}
}
}
/// <summary>
/// Represents a parsed connection string.
/// </summary>
class SQLiteConnectionString
{
public string ConnectionString { get ; private set ; }
public string DatabasePath { get ; private set ; }
public bool StoreDateTimeAsTicks { get ; private set ; }
#if NETFX_CORE
static readonly string MetroStyleDataPath = Windows . Storage . ApplicationData . Current . LocalFolder . Path ;
#endif
public SQLiteConnectionString ( string databasePath , bool storeDateTimeAsTicks )
{
ConnectionString = databasePath ;
StoreDateTimeAsTicks = storeDateTimeAsTicks ;
#if NETFX_CORE
DatabasePath = System . IO . Path . Combine ( MetroStyleDataPath , databasePath ) ;
#else
DatabasePath = databasePath ;
#endif
}
}
[AttributeUsage(AttributeTargets.Class)]
public class TableAttribute : Attribute
{
public string Name { get ; set ; }
public TableAttribute ( string name )
{
Name = name ;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class ColumnAttribute : Attribute
{
public string Name { get ; set ; }
public ColumnAttribute ( string name )
{
Name = name ;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class PrimaryKeyAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Property)]
public class AutoIncrementAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Property)]
public class IndexedAttribute : Attribute
{
public string Name { get ; set ; }
public int Order { get ; set ; }
public virtual bool Unique { get ; set ; }
public IndexedAttribute ( )
{
}
public IndexedAttribute ( string name , int order )
{
Name = name ;
Order = order ;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class IgnoreAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Property)]
public class UniqueAttribute : IndexedAttribute
{
public override bool Unique
{
get { return true ; }
set { /* throw? */ }
}
public UniqueAttribute ( ) : base ( )
{
}
public UniqueAttribute ( string name , int order ) : base ( name , order )
{
}
}
[AttributeUsage(AttributeTargets.Property)]
public class MaxLengthAttribute : Attribute
{
public int Value { get ; private set ; }
public MaxLengthAttribute ( int length )
{
Value = length ;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class CollationAttribute : Attribute
{
public string Value { get ; private set ; }
public CollationAttribute ( string collation )
{
Value = collation ;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class NotNullAttribute : Attribute
{
}
public class TableMapping
{
public Type MappedType { get ; private set ; }
public string TableName { get ; private set ; }
public Column [ ] Columns { get ; private set ; }
public Column PK { get ; private set ; }
public string GetByPrimaryKeySql { get ; private set ; }
Column _autoPk ;
Column [ ] _insertColumns ;
Column [ ] _insertOrReplaceColumns ;
public TableMapping ( Type type , CreateFlags createFlags = CreateFlags . None )
{
MappedType = type ;
#if NETFX_CORE
var tableAttr = ( TableAttribute ) System . Reflection . CustomAttributeExtensions
. GetCustomAttribute ( type . GetTypeInfo ( ) , typeof ( TableAttribute ) , true ) ;
#else
var tableAttr = ( TableAttribute ) type . GetCustomAttributes ( typeof ( TableAttribute ) , true ) . FirstOrDefault ( ) ;
#endif
TableName = tableAttr ! = null ? tableAttr . Name : MappedType . Name ;
#if ! NETFX_CORE
var props = MappedType . GetProperties ( BindingFlags . Public | BindingFlags . Instance | BindingFlags . SetProperty ) ;
#else
var props = from p in MappedType . GetRuntimeProperties ( )
where ( ( p . GetMethod ! = null & & p . GetMethod . IsPublic ) | | ( p . SetMethod ! = null & & p . SetMethod . IsPublic ) | | ( p . GetMethod ! = null & & p . GetMethod . IsStatic ) | | ( p . SetMethod ! = null & & p . SetMethod . IsStatic ) )
select p ;
#endif
var cols = new List < Column > ( ) ;
foreach ( var p in props )
{
#if ! NETFX_CORE
var ignore = p . GetCustomAttributes ( typeof ( IgnoreAttribute ) , true ) . Length > 0 ;
#else
var ignore = p . GetCustomAttributes ( typeof ( IgnoreAttribute ) , true ) . Count ( ) > 0 ;
#endif
if ( p . CanWrite & & ! ignore )
{
cols . Add ( new Column ( p , createFlags ) ) ;
}
}
Columns = cols . ToArray ( ) ;
foreach ( var c in Columns )
{
if ( c . IsAutoInc & & c . IsPK )
{
_autoPk = c ;
}
if ( c . IsPK )
{
PK = c ;
}
}
HasAutoIncPK = _autoPk ! = null ;
if ( PK ! = null )
{
GetByPrimaryKeySql = string . Format ( "select * from \"{0}\" where \"{1}\" = ?" , TableName , PK . Name ) ;
}
else
{
// People should not be calling Get/Find without a PK
GetByPrimaryKeySql = string . Format ( "select * from \"{0}\" limit 1" , TableName ) ;
}
}
public bool HasAutoIncPK { get ; private set ; }
public void SetAutoIncPK ( object obj , long id )
{
if ( _autoPk ! = null )
{
_autoPk . SetValue ( obj , Convert . ChangeType ( id , _autoPk . ColumnType , null ) ) ;
}
}
public Column [ ] InsertColumns
{
get
{
if ( _insertColumns = = null )
{
_insertColumns = Columns . Where ( c = > ! c . IsAutoInc ) . ToArray ( ) ;
}
return _insertColumns ;
}
}
public Column [ ] InsertOrReplaceColumns
{
get
{
if ( _insertOrReplaceColumns = = null )
{
_insertOrReplaceColumns = Columns . ToArray ( ) ;
}
return _insertOrReplaceColumns ;
}
}
public Column FindColumnWithPropertyName ( string propertyName )
{
var exact = Columns . FirstOrDefault ( c = > c . PropertyName = = propertyName ) ;
return exact ;
}
public Column FindColumn ( string columnName )
{
var exact = Columns . FirstOrDefault ( c = > c . Name = = columnName ) ;
return exact ;
}
PreparedSqlLiteInsertCommand _insertCommand ;
string _insertCommandExtra ;
public PreparedSqlLiteInsertCommand GetInsertCommand ( SQLiteConnection conn , string extra )
{
if ( _insertCommand = = null )
{
_insertCommand = CreateInsertCommand ( conn , extra ) ;
_insertCommandExtra = extra ;
}
else if ( _insertCommandExtra ! = extra )
{
_insertCommand . Dispose ( ) ;
_insertCommand = CreateInsertCommand ( conn , extra ) ;
_insertCommandExtra = extra ;
}
return _insertCommand ;
}
PreparedSqlLiteInsertCommand CreateInsertCommand ( SQLiteConnection conn , string extra )
{
var cols = InsertColumns ;
string insertSql ;
if ( ! cols . Any ( ) & & Columns . Count ( ) = = 1 & & Columns [ 0 ] . IsAutoInc )
{
insertSql = string . Format ( "insert {1} into \"{0}\" default values" , TableName , extra ) ;
}
else
{
var replacing = string . Compare ( extra , "OR REPLACE" , StringComparison . OrdinalIgnoreCase ) = = 0 ;
if ( replacing )
{
cols = InsertOrReplaceColumns ;
}
insertSql = string . Format ( "insert {3} into \"{0}\"({1}) values ({2})" , TableName ,
string . Join ( "," , ( from c in cols
select "\"" + c . Name + "\"" ) . ToArray ( ) ) ,
string . Join ( "," , ( from c in cols
select "?" ) . ToArray ( ) ) , extra ) ;
}
var insertCommand = new PreparedSqlLiteInsertCommand ( conn ) ;
insertCommand . CommandText = insertSql ;
return insertCommand ;
}
protected internal void Dispose ( )
{
if ( _insertCommand ! = null )
{
_insertCommand . Dispose ( ) ;
_insertCommand = null ;
}
}
public class Column
{
PropertyInfo _prop ;
public string Name { get ; private set ; }
public string PropertyName { get { return _prop . Name ; } }
public Type ColumnType { get ; private set ; }
public string Collation { get ; private set ; }
public bool IsAutoInc { get ; private set ; }
public bool IsAutoGuid { get ; private set ; }
public bool IsPK { get ; private set ; }
public IEnumerable < IndexedAttribute > Indices { get ; set ; }
public bool IsNullable { get ; private set ; }
public int? MaxStringLength { get ; private set ; }
public Column ( PropertyInfo prop , CreateFlags createFlags = CreateFlags . None )
{
var colAttr = ( ColumnAttribute ) prop . GetCustomAttributes ( typeof ( ColumnAttribute ) , true ) . FirstOrDefault ( ) ;
_prop = prop ;
Name = colAttr = = null ? prop . Name : colAttr . Name ;
//If this type is Nullable<T> then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the actual type instead
ColumnType = Nullable . GetUnderlyingType ( prop . PropertyType ) ? ? prop . PropertyType ;
Collation = Orm . Collation ( prop ) ;
IsPK = Orm . IsPK ( prop ) | |
( ( ( createFlags & CreateFlags . ImplicitPK ) = = CreateFlags . ImplicitPK ) & &
string . Compare ( prop . Name , Orm . ImplicitPkName , StringComparison . OrdinalIgnoreCase ) = = 0 ) ;
var isAuto = Orm . IsAutoInc ( prop ) | | ( IsPK & & ( ( createFlags & CreateFlags . AutoIncPK ) = = CreateFlags . AutoIncPK ) ) ;
IsAutoGuid = isAuto & & ColumnType = = typeof ( Guid ) ;
IsAutoInc = isAuto & & ! IsAutoGuid ;
Indices = Orm . GetIndices ( prop ) ;
if ( ! Indices . Any ( )
& & ! IsPK
& & ( ( createFlags & CreateFlags . ImplicitIndex ) = = CreateFlags . ImplicitIndex )
& & Name . EndsWith ( Orm . ImplicitIndexSuffix , StringComparison . OrdinalIgnoreCase )
)
{
Indices = new IndexedAttribute [ ] { new IndexedAttribute ( ) } ;
}
IsNullable = ! ( IsPK | | Orm . IsMarkedNotNull ( prop ) ) ;
MaxStringLength = Orm . MaxStringLength ( prop ) ;
}
public void SetValue ( object obj , object val )
{
_prop . SetValue ( obj , val , null ) ;
}
public object GetValue ( object obj )
{
return _prop . GetGetMethod ( ) . Invoke ( obj , null ) ;
}
}
}
public static class Orm
{
public const int DefaultMaxStringLength = 140 ;
public const string ImplicitPkName = "Id" ;
public const string ImplicitIndexSuffix = "Id" ;
public static string SqlDecl ( TableMapping . Column p , bool storeDateTimeAsTicks )
{
string decl = "\"" + p . Name + "\" " + SqlType ( p , storeDateTimeAsTicks ) + " " ;
if ( p . IsPK )
{
decl + = "primary key " ;
}
if ( p . IsAutoInc )
{
decl + = "autoincrement " ;
}
if ( ! p . IsNullable )
{
decl + = "not null " ;
}
if ( ! string . IsNullOrEmpty ( p . Collation ) )
{
decl + = "collate " + p . Collation + " " ;
}
return decl ;
}
public static string SqlType ( TableMapping . Column p , bool storeDateTimeAsTicks )
{
var clrType = p . ColumnType ;
if ( clrType = = typeof ( Boolean ) | | clrType = = typeof ( Byte ) | | clrType = = typeof ( UInt16 ) | | clrType = = typeof ( SByte ) | | clrType = = typeof ( Int16 ) | | clrType = = typeof ( Int32 ) )
{
return "integer" ;
}
else if ( clrType = = typeof ( UInt32 ) | | clrType = = typeof ( Int64 ) )
{
return "bigint" ;
}
else if ( clrType = = typeof ( Single ) | | clrType = = typeof ( Double ) | | clrType = = typeof ( Decimal ) )
{
return "float" ;
}
else if ( clrType = = typeof ( String ) )
{
int? len = p . MaxStringLength ;
if ( len . HasValue )
return "varchar(" + len . Value + ")" ;
return "varchar" ;
}
else if ( clrType = = typeof ( TimeSpan ) )
{
return "bigint" ;
}
else if ( clrType = = typeof ( DateTime ) )
{
return storeDateTimeAsTicks ? "bigint" : "datetime" ;
}
else if ( clrType = = typeof ( DateTimeOffset ) )
{
return "bigint" ;
#if ! NETFX_CORE
}
else if ( clrType . IsEnum )
{
#else
} else if ( clrType . GetTypeInfo ( ) . IsEnum ) {
#endif
return "integer" ;
}
else if ( clrType = = typeof ( byte [ ] ) )
{
return "blob" ;
}
else if ( clrType = = typeof ( Guid ) )
{
return "varchar(36)" ;
}
else
{
throw new NotSupportedException ( "Don't know about " + clrType ) ;
}
}
public static bool IsPK ( MemberInfo p )
{
var attrs = p . GetCustomAttributes ( typeof ( PrimaryKeyAttribute ) , true ) ;
#if ! NETFX_CORE
return attrs . Length > 0 ;
#else
return attrs . Count ( ) > 0 ;
#endif
}
public static string Collation ( MemberInfo p )
{
var attrs = p . GetCustomAttributes ( typeof ( CollationAttribute ) , true ) ;
#if ! NETFX_CORE
if ( attrs . Length > 0 )
{
return ( ( CollationAttribute ) attrs [ 0 ] ) . Value ;
#else
if ( attrs . Count ( ) > 0 ) {
return ( ( CollationAttribute ) attrs . First ( ) ) . Value ;
#endif
}
else
{
return string . Empty ;
}
}
public static bool IsAutoInc ( MemberInfo p )
{
var attrs = p . GetCustomAttributes ( typeof ( AutoIncrementAttribute ) , true ) ;
#if ! NETFX_CORE
return attrs . Length > 0 ;
#else
return attrs . Count ( ) > 0 ;
#endif
}
public static IEnumerable < IndexedAttribute > GetIndices ( MemberInfo p )
{
var attrs = p . GetCustomAttributes ( typeof ( IndexedAttribute ) , true ) ;
return attrs . Cast < IndexedAttribute > ( ) ;
}
public static int? MaxStringLength ( PropertyInfo p )
{
var attrs = p . GetCustomAttributes ( typeof ( MaxLengthAttribute ) , true ) ;
#if ! NETFX_CORE
if ( attrs . Length > 0 )
return ( ( MaxLengthAttribute ) attrs [ 0 ] ) . Value ;
#else
if ( attrs . Count ( ) > 0 )
return ( ( MaxLengthAttribute ) attrs . First ( ) ) . Value ;
#endif
return null ;
}
public static bool IsMarkedNotNull ( MemberInfo p )
{
var attrs = p . GetCustomAttributes ( typeof ( NotNullAttribute ) , true ) ;
#if ! NETFX_CORE
return attrs . Length > 0 ;
#else
return attrs . Count ( ) > 0 ;
#endif
}
}
public partial class SQLiteCommand
{
SQLiteConnection _conn ;
private List < Binding > _bindings ;
public string CommandText { get ; set ; }
internal SQLiteCommand ( SQLiteConnection conn )
{
_conn = conn ;
_bindings = new List < Binding > ( ) ;
CommandText = "" ;
}
public int ExecuteNonQuery ( )
{
if ( _conn . Trace )
{
_conn . InvokeTrace ( "Executing: " + this ) ;
}
var r = SQLite3 . Result . OK ;
lock ( _conn . SyncObject )
{
var stmt = Prepare ( ) ;
r = SQLite3 . Step ( stmt ) ;
Finalize ( stmt ) ;
}
if ( r = = SQLite3 . Result . Done )
{
int rowsAffected = SQLite3 . Changes ( _conn . Handle ) ;
return rowsAffected ;
}
else if ( r = = SQLite3 . Result . Error )
{
string msg = SQLite3 . GetErrmsg ( _conn . Handle ) ;
throw SQLiteException . New ( r , msg ) ;
}
else if ( r = = SQLite3 . Result . Constraint )
{
if ( SQLite3 . ExtendedErrCode ( _conn . Handle ) = = SQLite3 . ExtendedResult . ConstraintNotNull )
{
throw NotNullConstraintViolationException . New ( r , SQLite3 . GetErrmsg ( _conn . Handle ) ) ;
}
}
throw SQLiteException . New ( r , r . ToString ( ) ) ;
}
public IEnumerable < T > ExecuteDeferredQuery < T > ( )
{
return ExecuteDeferredQuery < T > ( _conn . GetMapping ( typeof ( T ) ) ) ;
}
public List < T > ExecuteQuery < T > ( )
{
return ExecuteDeferredQuery < T > ( _conn . GetMapping ( typeof ( T ) ) ) . ToList ( ) ;
}
public List < T > ExecuteQuery < T > ( TableMapping map )
{
return ExecuteDeferredQuery < T > ( map ) . ToList ( ) ;
}
/// <summary>
/// Invoked every time an instance is loaded from the database.
/// </summary>
/// <param name='obj'>
/// The newly created object.
/// </param>
/// <remarks>
/// This can be overridden in combination with the <see cref="SQLiteConnection.NewCommand"/>
/// method to hook into the life-cycle of objects.
///
/// Type safety is not possible because MonoTouch does not support virtual generic methods.
/// </remarks>
protected virtual void OnInstanceCreated ( object obj )
{
// Can be overridden.
}
public IEnumerable < T > ExecuteDeferredQuery < T > ( TableMapping map )
{
if ( _conn . Trace )
{
_conn . InvokeTrace ( "Executing Query: " + this ) ;
}
lock ( _conn . SyncObject )
{
var stmt = Prepare ( ) ;
try
{
var cols = new TableMapping . Column [ SQLite3 . ColumnCount ( stmt ) ] ;
for ( int i = 0 ; i < cols . Length ; i + + )
{
var name = SQLite3 . ColumnName16 ( stmt , i ) ;
cols [ i ] = map . FindColumn ( name ) ;
}
while ( SQLite3 . Step ( stmt ) = = SQLite3 . Result . Row )
{
var obj = Activator . CreateInstance ( map . MappedType ) ;
for ( int i = 0 ; i < cols . Length ; i + + )
{
if ( cols [ i ] = = null )
continue ;
var colType = SQLite3 . ColumnType ( stmt , i ) ;
var val = ReadCol ( stmt , i , colType , cols [ i ] . ColumnType ) ;
cols [ i ] . SetValue ( obj , val ) ;
}
OnInstanceCreated ( obj ) ;
yield return ( T ) obj ;
}
}
finally
{
SQLite3 . Finalize ( stmt ) ;
}
}
}
public T ExecuteScalar < T > ( )
{
if ( _conn . Trace )
{
_conn . InvokeTrace ( "Executing Query: " + this ) ;
}
T val = default ( T ) ;
lock ( _conn . SyncObject )
{
var stmt = Prepare ( ) ;
try
{
var r = SQLite3 . Step ( stmt ) ;
if ( r = = SQLite3 . Result . Row )
{
var colType = SQLite3 . ColumnType ( stmt , 0 ) ;
val = ( T ) ReadCol ( stmt , 0 , colType , typeof ( T ) ) ;
}
else if ( r = = SQLite3 . Result . Done )
{
}
else
{
throw SQLiteException . New ( r , SQLite3 . GetErrmsg ( _conn . Handle ) ) ;
}
}
finally
{
Finalize ( stmt ) ;
}
}
return val ;
}
public void Bind ( string name , object val )
{
_bindings . Add ( new Binding
{
Name = name ,
Value = val
} ) ;
}
public void Bind ( object val )
{
Bind ( null , val ) ;
}
public override string ToString ( )
{
var parts = new string [ 1 + _bindings . Count ] ;
parts [ 0 ] = CommandText ;
var i = 1 ;
foreach ( var b in _bindings )
{
parts [ i ] = string . Format ( " {0}: {1}" , i - 1 , b . Value ) ;
i + + ;
}
return string . Join ( Environment . NewLine , parts ) ;
}
Sqlite3Statement Prepare ( )
{
var stmt = SQLite3 . Prepare2 ( _conn . Handle , CommandText ) ;
BindAll ( stmt ) ;
return stmt ;
}
void Finalize ( Sqlite3Statement stmt )
{
SQLite3 . Finalize ( stmt ) ;
}
void BindAll ( Sqlite3Statement stmt )
{
int nextIdx = 1 ;
foreach ( var b in _bindings )
{
if ( b . Name ! = null )
{
b . Index = SQLite3 . BindParameterIndex ( stmt , b . Name ) ;
}
else
{
b . Index = nextIdx + + ;
}
BindParameter ( stmt , b . Index , b . Value , _conn . StoreDateTimeAsTicks ) ;
}
}
internal static IntPtr NegativePointer = new IntPtr ( - 1 ) ;
internal static void BindParameter ( Sqlite3Statement stmt , int index , object value , bool storeDateTimeAsTicks )
{
if ( value = = null )
{
SQLite3 . BindNull ( stmt , index ) ;
}
else
{
if ( value is Int32 )
{
SQLite3 . BindInt ( stmt , index , ( int ) value ) ;
}
else if ( value is String )
{
SQLite3 . BindText ( stmt , index , ( string ) value , - 1 , NegativePointer ) ;
}
else if ( value is Byte | | value is UInt16 | | value is SByte | | value is Int16 )
{
SQLite3 . BindInt ( stmt , index , Convert . ToInt32 ( value ) ) ;
}
else if ( value is Boolean )
{
SQLite3 . BindInt ( stmt , index , ( bool ) value ? 1 : 0 ) ;
}
else if ( value is UInt32 | | value is Int64 )
{
SQLite3 . BindInt64 ( stmt , index , Convert . ToInt64 ( value ) ) ;
}
else if ( value is Single | | value is Double | | value is Decimal )
{
SQLite3 . BindDouble ( stmt , index , Convert . ToDouble ( value ) ) ;
}
else if ( value is TimeSpan )
{
SQLite3 . BindInt64 ( stmt , index , ( ( TimeSpan ) value ) . Ticks ) ;
}
else if ( value is DateTime )
{
if ( storeDateTimeAsTicks )
{
SQLite3 . BindInt64 ( stmt , index , ( ( DateTime ) value ) . Ticks ) ;
}
else
{
SQLite3 . BindText ( stmt , index , ( ( DateTime ) value ) . ToString ( "yyyy-MM-dd HH:mm:ss" ) , - 1 , NegativePointer ) ;
}
}
else if ( value is DateTimeOffset )
{
SQLite3 . BindInt64 ( stmt , index , ( ( DateTimeOffset ) value ) . UtcTicks ) ;
#if ! NETFX_CORE
}
else if ( value . GetType ( ) . IsEnum )
{
#else
} else if ( value . GetType ( ) . GetTypeInfo ( ) . IsEnum ) {
#endif
SQLite3 . BindInt ( stmt , index , Convert . ToInt32 ( value ) ) ;
}
else if ( value is byte [ ] )
{
SQLite3 . BindBlob ( stmt , index , ( byte [ ] ) value , ( ( byte [ ] ) value ) . Length , NegativePointer ) ;
}
else if ( value is Guid )
{
SQLite3 . BindText ( stmt , index , ( ( Guid ) value ) . ToString ( ) , 72 , NegativePointer ) ;
}
else
{
throw new NotSupportedException ( "Cannot store type: " + value . GetType ( ) ) ;
}
}
}
class Binding
{
public string Name { get ; set ; }
public object Value { get ; set ; }
public int Index { get ; set ; }
}
object ReadCol ( Sqlite3Statement stmt , int index , SQLite3 . ColType type , Type clrType )
{
if ( type = = SQLite3 . ColType . Null )
{
return null ;
}
else
{
if ( clrType = = typeof ( String ) )
{
return SQLite3 . ColumnString ( stmt , index ) ;
}
else if ( clrType = = typeof ( Int32 ) )
{
return ( int ) SQLite3 . ColumnInt ( stmt , index ) ;
}
else if ( clrType = = typeof ( Boolean ) )
{
return SQLite3 . ColumnInt ( stmt , index ) = = 1 ;
}
else if ( clrType = = typeof ( double ) )
{
return SQLite3 . ColumnDouble ( stmt , index ) ;
}
else if ( clrType = = typeof ( float ) )
{
return ( float ) SQLite3 . ColumnDouble ( stmt , index ) ;
}
else if ( clrType = = typeof ( TimeSpan ) )
{
return new TimeSpan ( SQLite3 . ColumnInt64 ( stmt , index ) ) ;
}
else if ( clrType = = typeof ( DateTime ) )
{
if ( _conn . StoreDateTimeAsTicks )
{
return new DateTime ( SQLite3 . ColumnInt64 ( stmt , index ) ) ;
}
else
{
var text = SQLite3 . ColumnString ( stmt , index ) ;
return DateTime . Parse ( text ) ;
}
}
else if ( clrType = = typeof ( DateTimeOffset ) )
{
return new DateTimeOffset ( SQLite3 . ColumnInt64 ( stmt , index ) , TimeSpan . Zero ) ;
#if ! NETFX_CORE
}
else if ( clrType . IsEnum )
{
#else
} else if ( clrType . GetTypeInfo ( ) . IsEnum ) {
#endif
return SQLite3 . ColumnInt ( stmt , index ) ;
}
else if ( clrType = = typeof ( Int64 ) )
{
return SQLite3 . ColumnInt64 ( stmt , index ) ;
}
else if ( clrType = = typeof ( UInt32 ) )
{
return ( uint ) SQLite3 . ColumnInt64 ( stmt , index ) ;
}
else if ( clrType = = typeof ( decimal ) )
{
return ( decimal ) SQLite3 . ColumnDouble ( stmt , index ) ;
}
else if ( clrType = = typeof ( Byte ) )
{
return ( byte ) SQLite3 . ColumnInt ( stmt , index ) ;
}
else if ( clrType = = typeof ( UInt16 ) )
{
return ( ushort ) SQLite3 . ColumnInt ( stmt , index ) ;
}
else if ( clrType = = typeof ( Int16 ) )
{
return ( short ) SQLite3 . ColumnInt ( stmt , index ) ;
}
else if ( clrType = = typeof ( sbyte ) )
{
return ( sbyte ) SQLite3 . ColumnInt ( stmt , index ) ;
}
else if ( clrType = = typeof ( byte [ ] ) )
{
return SQLite3 . ColumnByteArray ( stmt , index ) ;
}
else if ( clrType = = typeof ( Guid ) )
{
var text = SQLite3 . ColumnString ( stmt , index ) ;
return new Guid ( text ) ;
}
else
{
throw new NotSupportedException ( "Don't know how to read " + clrType ) ;
}
}
}
}
/// <summary>
/// Since the insert never changed, we only need to prepare once.
/// </summary>
public class PreparedSqlLiteInsertCommand : IDisposable
{
public bool Initialized { get ; set ; }
protected SQLiteConnection Connection { get ; set ; }
public string CommandText { get ; set ; }
protected Sqlite3Statement Statement { get ; set ; }
internal static readonly Sqlite3Statement NullStatement = default ( Sqlite3Statement ) ;
internal PreparedSqlLiteInsertCommand ( SQLiteConnection conn )
{
Connection = conn ;
}
public int ExecuteNonQuery ( object [ ] source )
{
if ( Connection . Trace )
{
Connection . InvokeTrace ( "Executing: " + CommandText ) ;
}
var r = SQLite3 . Result . OK ;
if ( ! Initialized )
{
Statement = Prepare ( ) ;
Initialized = true ;
}
//bind the values.
if ( source ! = null )
{
for ( int i = 0 ; i < source . Length ; i + + )
{
SQLiteCommand . BindParameter ( Statement , i + 1 , source [ i ] , Connection . StoreDateTimeAsTicks ) ;
}
}
r = SQLite3 . Step ( Statement ) ;
if ( r = = SQLite3 . Result . Done )
{
int rowsAffected = SQLite3 . Changes ( Connection . Handle ) ;
SQLite3 . Reset ( Statement ) ;
return rowsAffected ;
}
else if ( r = = SQLite3 . Result . Error )
{
string msg = SQLite3 . GetErrmsg ( Connection . Handle ) ;
SQLite3 . Reset ( Statement ) ;
throw SQLiteException . New ( r , msg ) ;
}
else if ( r = = SQLite3 . Result . Constraint & & SQLite3 . ExtendedErrCode ( Connection . Handle ) = = SQLite3 . ExtendedResult . ConstraintNotNull )
{
SQLite3 . Reset ( Statement ) ;
throw NotNullConstraintViolationException . New ( r , SQLite3 . GetErrmsg ( Connection . Handle ) ) ;
}
else
{
SQLite3 . Reset ( Statement ) ;
throw SQLiteException . New ( r , r . ToString ( ) ) ;
}
}
protected virtual Sqlite3Statement Prepare ( )
{
var stmt = SQLite3 . Prepare2 ( Connection . Handle , CommandText ) ;
return stmt ;
}
public void Dispose ( )
{
Dispose ( true ) ;
GC . SuppressFinalize ( this ) ;
}
private void Dispose ( bool disposing )
{
if ( Statement ! = NullStatement )
{
try
{
SQLite3 . Finalize ( Statement ) ;
}
finally
{
Statement = NullStatement ;
Connection = null ;
}
}
}
~ PreparedSqlLiteInsertCommand ( )
{
Dispose ( false ) ;
}
}
public abstract class BaseTableQuery
{
protected class Ordering
{
public string ColumnName { get ; set ; }
public bool Ascending { get ; set ; }
}
}
public class TableQuery < T > : BaseTableQuery , IEnumerable < T >
{
public SQLiteConnection Connection { get ; private set ; }
public TableMapping Table { get ; private set ; }
Expression _where ;
List < Ordering > _orderBys ;
int? _limit ;
int? _offset ;
BaseTableQuery _joinInner ;
Expression _joinInnerKeySelector ;
BaseTableQuery _joinOuter ;
Expression _joinOuterKeySelector ;
Expression _joinSelector ;
Expression _selector ;
TableQuery ( SQLiteConnection conn , TableMapping table )
{
Connection = conn ;
Table = table ;
}
public TableQuery ( SQLiteConnection conn )
{
Connection = conn ;
Table = Connection . GetMapping ( typeof ( T ) ) ;
}
public TableQuery < U > Clone < U > ( )
{
var q = new TableQuery < U > ( Connection , Table ) ;
q . _where = _where ;
q . _deferred = _deferred ;
if ( _orderBys ! = null )
{
q . _orderBys = new List < Ordering > ( _orderBys ) ;
}
q . _limit = _limit ;
q . _offset = _offset ;
q . _joinInner = _joinInner ;
q . _joinInnerKeySelector = _joinInnerKeySelector ;
q . _joinOuter = _joinOuter ;
q . _joinOuterKeySelector = _joinOuterKeySelector ;
q . _joinSelector = _joinSelector ;
q . _selector = _selector ;
return q ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 조건식에 따라 테이블 쿼리를 필터링합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
/// <param name="predExpr">
2025-08-11 18:49:20 +09:00
/// 결과를 필터링하는 조건식.
2025-06-04 23:10:11 +09:00
/// </param>
/// <returns>
2025-08-11 18:49:20 +09:00
/// 필터링된 TableQuery 객체.
2025-06-04 23:10:11 +09:00
/// </returns>
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 조건식으로 데이터 필터링
2025-06-10 01:09:36 +09:00
/// var adults = db.Table<Person>()
2025-06-04 23:10:11 +09:00
/// .Where(p => p.Age >= 18);
///
2025-08-11 18:49:20 +09:00
/// // 여러 조건 체이닝
2025-06-10 01:09:36 +09:00
/// var adultMales = db.Table<Person>()
2025-06-04 23:10:11 +09:00
/// .Where(p => p.Age >= 18)
2025-08-11 18:49:20 +09:00
/// .Where(p => p.Gender == "남성");
2025-06-04 23:10:11 +09:00
///
/// foreach (var person in adults) {
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"성인: {person.Name}, {person.Age}세");
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public TableQuery < T > Where ( Expression < Func < T , bool > > predExpr )
{
if ( predExpr . NodeType = = ExpressionType . Lambda )
{
var lambda = ( LambdaExpression ) predExpr ;
var pred = lambda . Body ;
var q = Clone < T > ( ) ;
q . AddWhere ( pred ) ;
return q ;
}
else
{
throw new NotSupportedException ( "Must be a predicate" ) ;
}
}
public TableQuery < T > Take ( int n )
{
var q = Clone < T > ( ) ;
q . _limit = n ;
return q ;
}
public TableQuery < T > Skip ( int n )
{
var q = Clone < T > ( ) ;
q . _offset = n ;
return q ;
}
public T ElementAt ( int index )
{
return Skip ( index ) . Take ( 1 ) . First ( ) ;
}
bool _deferred ;
public TableQuery < T > Deferred ( )
{
var q = Clone < T > ( ) ;
q . _deferred = true ;
return q ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 속성에 따라 테이블 쿼리 결과를 오름차순으로 정렬합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <typeparam name="U">정렬 기준 속성의 타입</typeparam>
/// <param name="orderExpr">정렬 기준 속성을 선택하는 식</param>
/// <returns>정렬된 쿼리 객체</returns>
2025-06-04 23:10:11 +09:00
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 나이순으로 정렬
2025-06-10 01:09:36 +09:00
/// var peopleByAge = db.Table<Person>().OrderBy(p => p.Age);
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 이름순으로 정렬
2025-06-10 01:09:36 +09:00
/// var peopleByName = db.Table<Person>().OrderBy(p => p.Name);
2025-06-04 23:10:11 +09:00
///
/// foreach (var person in peopleByAge) {
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"{person.Name}, {person.Age}세");
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public TableQuery < T > OrderBy < U > ( Expression < Func < T , U > > orderExpr )
{
return AddOrderBy < U > ( orderExpr , true ) ;
}
/// <summary>
2025-08-11 18:49:20 +09:00
/// 지정된 속성에 따라 테이블 쿼리 결과를 내림차순으로 정렬합니다.
2025-06-04 23:10:11 +09:00
/// </summary>
2025-08-11 18:49:20 +09:00
/// <typeparam name="U">정렬 기준 속성의 타입</typeparam>
/// <param name="orderExpr">정렬 기준 속성을 선택하는 식</param>
/// <returns>정렬된 쿼리 객체</returns>
2025-06-04 23:10:11 +09:00
/// <example>
/// <code>
2025-08-11 18:49:20 +09:00
/// // 나이 내림차순으로 정렬
2025-06-10 01:09:36 +09:00
/// var olderFirst = db.Table<Person>().OrderByDescending(p => p.Age);
2025-06-04 23:10:11 +09:00
///
2025-08-11 18:49:20 +09:00
/// // 이름 내림차순으로 정렬
2025-06-10 01:09:36 +09:00
/// var reverseAlpha = db.Table<Person>().OrderByDescending(p => p.Name);
2025-06-04 23:10:11 +09:00
///
/// foreach (var person in olderFirst) {
2025-08-11 18:49:20 +09:00
/// Console.WriteLine($"{person.Name}, {person.Age}세");
2025-06-04 23:10:11 +09:00
/// }
/// </code>
/// </example>
public TableQuery < T > OrderByDescending < U > ( Expression < Func < T , U > > orderExpr )
{
return AddOrderBy < U > ( orderExpr , false ) ;
}
public TableQuery < T > ThenBy < U > ( Expression < Func < T , U > > orderExpr )
{
return AddOrderBy < U > ( orderExpr , true ) ;
}
public TableQuery < T > ThenByDescending < U > ( Expression < Func < T , U > > orderExpr )
{
return AddOrderBy < U > ( orderExpr , false ) ;
}
private TableQuery < T > AddOrderBy < U > ( Expression < Func < T , U > > orderExpr , bool asc )
{
if ( orderExpr . NodeType = = ExpressionType . Lambda )
{
var lambda = ( LambdaExpression ) orderExpr ;
MemberExpression mem = null ;
var unary = lambda . Body as UnaryExpression ;
if ( unary ! = null & & unary . NodeType = = ExpressionType . Convert )
{
mem = unary . Operand as MemberExpression ;
}
else
{
mem = lambda . Body as MemberExpression ;
}
if ( mem ! = null & & ( mem . Expression . NodeType = = ExpressionType . Parameter ) )
{
var q = Clone < T > ( ) ;
if ( q . _orderBys = = null )
{
q . _orderBys = new List < Ordering > ( ) ;
}
q . _orderBys . Add ( new Ordering
{
ColumnName = Table . FindColumnWithPropertyName ( mem . Member . Name ) . Name ,
Ascending = asc
} ) ;
return q ;
}
else
{
throw new NotSupportedException ( "Order By does not support: " + orderExpr ) ;
}
}
else
{
throw new NotSupportedException ( "Must be a predicate" ) ;
}
}
private void AddWhere ( Expression pred )
{
if ( _where = = null )
{
_where = pred ;
}
else
{
_where = Expression . AndAlso ( _where , pred ) ;
}
}
public TableQuery < TResult > Join < TInner , TKey , TResult > (
TableQuery < TInner > inner ,
Expression < Func < T , TKey > > outerKeySelector ,
Expression < Func < TInner , TKey > > innerKeySelector ,
Expression < Func < T , TInner , TResult > > resultSelector )
{
var q = new TableQuery < TResult > ( Connection , Connection . GetMapping ( typeof ( TResult ) ) )
{
_joinOuter = this ,
_joinOuterKeySelector = outerKeySelector ,
_joinInner = inner ,
_joinInnerKeySelector = innerKeySelector ,
_joinSelector = resultSelector ,
} ;
return q ;
}
public TableQuery < TResult > Select < TResult > ( Expression < Func < T , TResult > > selector )
{
var q = Clone < TResult > ( ) ;
q . _selector = selector ;
return q ;
}
private SQLiteCommand GenerateCommand ( string selectionList )
{
if ( _joinInner ! = null & & _joinOuter ! = null )
{
throw new NotSupportedException ( "Joins are not supported." ) ;
}
else
{
var cmdText = "select " + selectionList + " from \"" + Table . TableName + "\"" ;
var args = new List < object > ( ) ;
if ( _where ! = null )
{
var w = CompileExpr ( _where , args ) ;
cmdText + = " where " + w . CommandText ;
}
if ( ( _orderBys ! = null ) & & ( _orderBys . Count > 0 ) )
{
var t = string . Join ( ", " , _orderBys . Select ( o = > "\"" + o . ColumnName + "\"" + ( o . Ascending ? "" : " desc" ) ) . ToArray ( ) ) ;
cmdText + = " order by " + t ;
}
if ( _limit . HasValue )
{
cmdText + = " limit " + _limit . Value ;
}
if ( _offset . HasValue )
{
if ( ! _limit . HasValue )
{
cmdText + = " limit -1 " ;
}
cmdText + = " offset " + _offset . Value ;
}
return Connection . CreateCommand ( cmdText , args . ToArray ( ) ) ;
}
}
class CompileResult
{
public string CommandText { get ; set ; }
public object Value { get ; set ; }
}
private CompileResult CompileExpr ( Expression expr , List < object > queryArgs )
{
if ( expr = = null )
{
throw new NotSupportedException ( "Expression is NULL" ) ;
}
else if ( expr is BinaryExpression )
{
var bin = ( BinaryExpression ) expr ;
var leftr = CompileExpr ( bin . Left , queryArgs ) ;
var rightr = CompileExpr ( bin . Right , queryArgs ) ;
//If either side is a parameter and is null, then handle the other side specially (for "is null"/"is not null")
string text ;
if ( leftr . CommandText = = "?" & & leftr . Value = = null )
text = CompileNullBinaryExpression ( bin , rightr ) ;
else if ( rightr . CommandText = = "?" & & rightr . Value = = null )
text = CompileNullBinaryExpression ( bin , leftr ) ;
else
text = "(" + leftr . CommandText + " " + GetSqlName ( bin ) + " " + rightr . CommandText + ")" ;
return new CompileResult { CommandText = text } ;
}
else if ( expr . NodeType = = ExpressionType . Call )
{
var call = ( MethodCallExpression ) expr ;
var args = new CompileResult [ call . Arguments . Count ] ;
var obj = call . Object ! = null ? CompileExpr ( call . Object , queryArgs ) : null ;
for ( var i = 0 ; i < args . Length ; i + + )
{
args [ i ] = CompileExpr ( call . Arguments [ i ] , queryArgs ) ;
}
var sqlCall = "" ;
if ( call . Method . Name = = "Like" & & args . Length = = 2 )
{
sqlCall = "(" + args [ 0 ] . CommandText + " like " + args [ 1 ] . CommandText + ")" ;
}
else if ( call . Method . Name = = "Contains" & & args . Length = = 2 )
{
sqlCall = "(" + args [ 1 ] . CommandText + " in " + args [ 0 ] . CommandText + ")" ;
}
else if ( call . Method . Name = = "Contains" & & args . Length = = 1 )
{
if ( call . Object ! = null & & call . Object . Type = = typeof ( string ) )
{
sqlCall = "(" + obj . CommandText + " like ('%' || " + args [ 0 ] . CommandText + " || '%'))" ;
}
else
{
sqlCall = "(" + args [ 0 ] . CommandText + " in " + obj . CommandText + ")" ;
}
}
else if ( call . Method . Name = = "StartsWith" & & args . Length = = 1 )
{
sqlCall = "(" + obj . CommandText + " like (" + args [ 0 ] . CommandText + " || '%'))" ;
}
else if ( call . Method . Name = = "EndsWith" & & args . Length = = 1 )
{
sqlCall = "(" + obj . CommandText + " like ('%' || " + args [ 0 ] . CommandText + "))" ;
}
else if ( call . Method . Name = = "Equals" & & args . Length = = 1 )
{
sqlCall = "(" + obj . CommandText + " = (" + args [ 0 ] . CommandText + "))" ;
}
else if ( call . Method . Name = = "ToLower" )
{
sqlCall = "(lower(" + obj . CommandText + "))" ;
}
else if ( call . Method . Name = = "ToUpper" )
{
sqlCall = "(upper(" + obj . CommandText + "))" ;
}
else
{
sqlCall = call . Method . Name . ToLower ( ) + "(" + string . Join ( "," , args . Select ( a = > a . CommandText ) . ToArray ( ) ) + ")" ;
}
return new CompileResult { CommandText = sqlCall } ;
}
else if ( expr . NodeType = = ExpressionType . Constant )
{
var c = ( ConstantExpression ) expr ;
queryArgs . Add ( c . Value ) ;
return new CompileResult
{
CommandText = "?" ,
Value = c . Value
} ;
}
else if ( expr . NodeType = = ExpressionType . Convert )
{
var u = ( UnaryExpression ) expr ;
var ty = u . Type ;
var valr = CompileExpr ( u . Operand , queryArgs ) ;
return new CompileResult
{
CommandText = valr . CommandText ,
Value = valr . Value ! = null ? ConvertTo ( valr . Value , ty ) : null
} ;
}
else if ( expr . NodeType = = ExpressionType . Not )
{
var u = ( UnaryExpression ) expr ;
var ty = u . Type ;
var valr = CompileExpr ( u . Operand , queryArgs ) ;
return new CompileResult
{
CommandText = "NOT " + valr . CommandText ,
Value = valr . Value ! = null ? valr . Value : null
} ;
}
else if ( expr . NodeType = = ExpressionType . MemberAccess )
{
var mem = ( MemberExpression ) expr ;
if ( mem . Expression ! = null & & mem . Expression . NodeType = = ExpressionType . Parameter )
{
//
// This is a column of our table, output just the column name
// Need to translate it if that column name is mapped
//
var columnName = Table . FindColumnWithPropertyName ( mem . Member . Name ) . Name ;
return new CompileResult { CommandText = "\"" + columnName + "\"" } ;
}
else
{
object obj = null ;
if ( mem . Expression ! = null )
{
var r = CompileExpr ( mem . Expression , queryArgs ) ;
if ( r . Value = = null )
{
throw new NotSupportedException ( "Member access failed to compile expression" ) ;
}
if ( r . CommandText = = "?" )
{
queryArgs . RemoveAt ( queryArgs . Count - 1 ) ;
}
obj = r . Value ;
}
//
// Get the member value
//
object val = null ;
#if ! NETFX_CORE
if ( mem . Member . MemberType = = MemberTypes . Property )
{
#else
if ( mem . Member is PropertyInfo ) {
#endif
var m = ( PropertyInfo ) mem . Member ;
//val = m.GetValue (obj, null);
val = m . GetGetMethod ( ) . Invoke ( obj , null ) ;
#if ! NETFX_CORE
}
else if ( mem . Member . MemberType = = MemberTypes . Field )
{
#else
} else if ( mem . Member is FieldInfo ) {
#endif
#if SILVERLIGHT
val = Expression . Lambda ( expr ) . Compile ( ) . DynamicInvoke ( ) ;
#else
var m = ( FieldInfo ) mem . Member ;
val = m . GetValue ( obj ) ;
#endif
}
else
{
#if ! NETFX_CORE
throw new NotSupportedException ( "MemberExpr: " + mem . Member . MemberType ) ;
#else
throw new NotSupportedException ( "MemberExpr: " + mem . Member . DeclaringType ) ;
#endif
}
//
// Work special magic for enumerables
//
if ( val ! = null & & val is System . Collections . IEnumerable & & ! ( val is string ) & & ! ( val is System . Collections . Generic . IEnumerable < byte > ) )
{
var sb = new System . Text . StringBuilder ( ) ;
sb . Append ( "(" ) ;
var head = "" ;
foreach ( var a in ( System . Collections . IEnumerable ) val )
{
queryArgs . Add ( a ) ;
sb . Append ( head ) ;
sb . Append ( "?" ) ;
head = "," ;
}
sb . Append ( ")" ) ;
return new CompileResult
{
CommandText = sb . ToString ( ) ,
Value = val
} ;
}
else
{
queryArgs . Add ( val ) ;
return new CompileResult
{
CommandText = "?" ,
Value = val
} ;
}
}
}
throw new NotSupportedException ( "Cannot compile: " + expr . NodeType . ToString ( ) ) ;
}
static object ConvertTo ( object obj , Type t )
{
Type nut = Nullable . GetUnderlyingType ( t ) ;
if ( nut ! = null )
{
if ( obj = = null ) return null ;
return Convert . ChangeType ( obj , nut ) ;
}
else
{
return Convert . ChangeType ( obj , t ) ;
}
}
/// <summary>
/// Compiles a BinaryExpression where one of the parameters is null.
/// </summary>
/// <param name="parameter">The non-null parameter</param>
private string CompileNullBinaryExpression ( BinaryExpression expression , CompileResult parameter )
{
if ( expression . NodeType = = ExpressionType . Equal )
return "(" + parameter . CommandText + " is ?)" ;
else if ( expression . NodeType = = ExpressionType . NotEqual )
return "(" + parameter . CommandText + " is not ?)" ;
else
throw new NotSupportedException ( "Cannot compile Null-BinaryExpression with type " + expression . NodeType . ToString ( ) ) ;
}
string GetSqlName ( Expression expr )
{
var n = expr . NodeType ;
if ( n = = ExpressionType . GreaterThan )
return ">" ;
else if ( n = = ExpressionType . GreaterThanOrEqual )
{
return ">=" ;
}
else if ( n = = ExpressionType . LessThan )
{
return "<" ;
}
else if ( n = = ExpressionType . LessThanOrEqual )
{
return "<=" ;
}
else if ( n = = ExpressionType . And )
{
return "&" ;
}
else if ( n = = ExpressionType . AndAlso )
{
return "and" ;
}
else if ( n = = ExpressionType . Or )
{
return "|" ;
}
else if ( n = = ExpressionType . OrElse )
{
return "or" ;
}
else if ( n = = ExpressionType . Equal )
{
return "=" ;
}
else if ( n = = ExpressionType . NotEqual )
{
return "!=" ;
}
else
{
throw new NotSupportedException ( "Cannot get SQL for: " + n ) ;
}
}
public int Count ( )
{
return GenerateCommand ( "count(*)" ) . ExecuteScalar < int > ( ) ;
}
public int Count ( Expression < Func < T , bool > > predExpr )
{
return Where ( predExpr ) . Count ( ) ;
}
public IEnumerator < T > GetEnumerator ( )
{
if ( ! _deferred )
return GenerateCommand ( "*" ) . ExecuteQuery < T > ( ) . GetEnumerator ( ) ;
return GenerateCommand ( "*" ) . ExecuteDeferredQuery < T > ( ) . GetEnumerator ( ) ;
}
System . Collections . IEnumerator System . Collections . IEnumerable . GetEnumerator ( )
{
return GetEnumerator ( ) ;
}
public T First ( )
{
var query = Take ( 1 ) ;
return query . ToList < T > ( ) . First ( ) ;
}
public T FirstOrDefault ( )
{
var query = Take ( 1 ) ;
return query . ToList < T > ( ) . FirstOrDefault ( ) ;
}
}
public static class SQLite3
{
public enum Result : int
{
OK = 0 ,
Error = 1 ,
Internal = 2 ,
Perm = 3 ,
Abort = 4 ,
Busy = 5 ,
Locked = 6 ,
NoMem = 7 ,
ReadOnly = 8 ,
Interrupt = 9 ,
IOError = 10 ,
Corrupt = 11 ,
NotFound = 12 ,
Full = 13 ,
CannotOpen = 14 ,
LockErr = 15 ,
Empty = 16 ,
SchemaChngd = 17 ,
TooBig = 18 ,
Constraint = 19 ,
Mismatch = 20 ,
Misuse = 21 ,
NotImplementedLFS = 22 ,
AccessDenied = 23 ,
Format = 24 ,
Range = 25 ,
NonDBFile = 26 ,
Notice = 27 ,
Warning = 28 ,
Row = 100 ,
Done = 101
}
public enum ExtendedResult : int
{
IOErrorRead = ( Result . IOError | ( 1 < < 8 ) ) ,
IOErrorShortRead = ( Result . IOError | ( 2 < < 8 ) ) ,
IOErrorWrite = ( Result . IOError | ( 3 < < 8 ) ) ,
IOErrorFsync = ( Result . IOError | ( 4 < < 8 ) ) ,
IOErrorDirFSync = ( Result . IOError | ( 5 < < 8 ) ) ,
IOErrorTruncate = ( Result . IOError | ( 6 < < 8 ) ) ,
IOErrorFStat = ( Result . IOError | ( 7 < < 8 ) ) ,
IOErrorUnlock = ( Result . IOError | ( 8 < < 8 ) ) ,
IOErrorRdlock = ( Result . IOError | ( 9 < < 8 ) ) ,
IOErrorDelete = ( Result . IOError | ( 10 < < 8 ) ) ,
IOErrorBlocked = ( Result . IOError | ( 11 < < 8 ) ) ,
IOErrorNoMem = ( Result . IOError | ( 12 < < 8 ) ) ,
IOErrorAccess = ( Result . IOError | ( 13 < < 8 ) ) ,
IOErrorCheckReservedLock = ( Result . IOError | ( 14 < < 8 ) ) ,
IOErrorLock = ( Result . IOError | ( 15 < < 8 ) ) ,
IOErrorClose = ( Result . IOError | ( 16 < < 8 ) ) ,
IOErrorDirClose = ( Result . IOError | ( 17 < < 8 ) ) ,
IOErrorSHMOpen = ( Result . IOError | ( 18 < < 8 ) ) ,
IOErrorSHMSize = ( Result . IOError | ( 19 < < 8 ) ) ,
IOErrorSHMLock = ( Result . IOError | ( 20 < < 8 ) ) ,
IOErrorSHMMap = ( Result . IOError | ( 21 < < 8 ) ) ,
IOErrorSeek = ( Result . IOError | ( 22 < < 8 ) ) ,
IOErrorDeleteNoEnt = ( Result . IOError | ( 23 < < 8 ) ) ,
IOErrorMMap = ( Result . IOError | ( 24 < < 8 ) ) ,
LockedSharedcache = ( Result . Locked | ( 1 < < 8 ) ) ,
BusyRecovery = ( Result . Busy | ( 1 < < 8 ) ) ,
CannottOpenNoTempDir = ( Result . CannotOpen | ( 1 < < 8 ) ) ,
CannotOpenIsDir = ( Result . CannotOpen | ( 2 < < 8 ) ) ,
CannotOpenFullPath = ( Result . CannotOpen | ( 3 < < 8 ) ) ,
CorruptVTab = ( Result . Corrupt | ( 1 < < 8 ) ) ,
ReadonlyRecovery = ( Result . ReadOnly | ( 1 < < 8 ) ) ,
ReadonlyCannotLock = ( Result . ReadOnly | ( 2 < < 8 ) ) ,
ReadonlyRollback = ( Result . ReadOnly | ( 3 < < 8 ) ) ,
AbortRollback = ( Result . Abort | ( 2 < < 8 ) ) ,
ConstraintCheck = ( Result . Constraint | ( 1 < < 8 ) ) ,
ConstraintCommitHook = ( Result . Constraint | ( 2 < < 8 ) ) ,
ConstraintForeignKey = ( Result . Constraint | ( 3 < < 8 ) ) ,
ConstraintFunction = ( Result . Constraint | ( 4 < < 8 ) ) ,
ConstraintNotNull = ( Result . Constraint | ( 5 < < 8 ) ) ,
ConstraintPrimaryKey = ( Result . Constraint | ( 6 < < 8 ) ) ,
ConstraintTrigger = ( Result . Constraint | ( 7 < < 8 ) ) ,
ConstraintUnique = ( Result . Constraint | ( 8 < < 8 ) ) ,
ConstraintVTab = ( Result . Constraint | ( 9 < < 8 ) ) ,
NoticeRecoverWAL = ( Result . Notice | ( 1 < < 8 ) ) ,
NoticeRecoverRollback = ( Result . Notice | ( 2 < < 8 ) )
}
public enum ConfigOption : int
{
SingleThread = 1 ,
MultiThread = 2 ,
Serialized = 3
}
#if ! USE_CSHARP_SQLITE & & ! USE_WP8_NATIVE_SQLITE
[DllImport("sqlite3", EntryPoint = "sqlite3_open", CallingConvention = CallingConvention.Cdecl)]
public static extern Result Open ( [ MarshalAs ( UnmanagedType . LPStr ) ] string filename , out IntPtr db ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)]
public static extern Result Open ( [ MarshalAs ( UnmanagedType . LPStr ) ] string filename , out IntPtr db , int flags , IntPtr zvfs ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)]
public static extern Result Open ( byte [ ] filename , out IntPtr db , int flags , IntPtr zvfs ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)]
public static extern Result Open16 ( [ MarshalAs ( UnmanagedType . LPWStr ) ] string filename , out IntPtr db ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_enable_load_extension", CallingConvention = CallingConvention.Cdecl)]
public static extern Result EnableLoadExtension ( IntPtr db , int onoff ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_close", CallingConvention = CallingConvention.Cdecl)]
public static extern Result Close ( IntPtr db ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_initialize", CallingConvention = CallingConvention.Cdecl)]
public static extern Result Initialize ( ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_shutdown", CallingConvention = CallingConvention.Cdecl)]
public static extern Result Shutdown ( ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_config", CallingConvention = CallingConvention.Cdecl)]
public static extern Result Config ( ConfigOption option ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_win32_set_directory", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern int SetDirectory ( uint directoryType , string directoryPath ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_busy_timeout", CallingConvention = CallingConvention.Cdecl)]
public static extern Result BusyTimeout ( IntPtr db , int milliseconds ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_changes", CallingConvention = CallingConvention.Cdecl)]
public static extern int Changes ( IntPtr db ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)]
public static extern Result Prepare2 ( IntPtr db , [ MarshalAs ( UnmanagedType . LPStr ) ] string sql , int numBytes , out IntPtr stmt , IntPtr pzTail ) ;
#if NETFX_CORE
[DllImport ("sqlite3", EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)]
public static extern Result Prepare2 ( IntPtr db , byte [ ] queryBytes , int numBytes , out IntPtr stmt , IntPtr pzTail ) ;
#endif
public static IntPtr Prepare2 ( IntPtr db , string query )
{
IntPtr stmt ;
#if NETFX_CORE
byte [ ] queryBytes = System . Text . UTF8Encoding . UTF8 . GetBytes ( query ) ;
var r = Prepare2 ( db , queryBytes , queryBytes . Length , out stmt , IntPtr . Zero ) ;
#else
var r = Prepare2 ( db , query , System . Text . UTF8Encoding . UTF8 . GetByteCount ( query ) , out stmt , IntPtr . Zero ) ;
#endif
if ( r ! = Result . OK )
{
throw SQLiteException . New ( r , GetErrmsg ( db ) ) ;
}
return stmt ;
}
[DllImport("sqlite3", EntryPoint = "sqlite3_step", CallingConvention = CallingConvention.Cdecl)]
public static extern Result Step ( IntPtr stmt ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_reset", CallingConvention = CallingConvention.Cdecl)]
public static extern Result Reset ( IntPtr stmt ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_finalize", CallingConvention = CallingConvention.Cdecl)]
public static extern Result Finalize ( IntPtr stmt ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_last_insert_rowid", CallingConvention = CallingConvention.Cdecl)]
public static extern long LastInsertRowid ( IntPtr db ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_errmsg16", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Errmsg ( IntPtr db ) ;
public static string GetErrmsg ( IntPtr db )
{
return Marshal . PtrToStringUni ( Errmsg ( db ) ) ;
}
[DllImport("sqlite3", EntryPoint = "sqlite3_bind_parameter_index", CallingConvention = CallingConvention.Cdecl)]
public static extern int BindParameterIndex ( IntPtr stmt , [ MarshalAs ( UnmanagedType . LPStr ) ] string name ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_bind_null", CallingConvention = CallingConvention.Cdecl)]
public static extern int BindNull ( IntPtr stmt , int index ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_bind_int", CallingConvention = CallingConvention.Cdecl)]
public static extern int BindInt ( IntPtr stmt , int index , int val ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_bind_int64", CallingConvention = CallingConvention.Cdecl)]
public static extern int BindInt64 ( IntPtr stmt , int index , long val ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_bind_double", CallingConvention = CallingConvention.Cdecl)]
public static extern int BindDouble ( IntPtr stmt , int index , double val ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_bind_text16", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern int BindText ( IntPtr stmt , int index , [ MarshalAs ( UnmanagedType . LPWStr ) ] string val , int n , IntPtr free ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_bind_blob", CallingConvention = CallingConvention.Cdecl)]
public static extern int BindBlob ( IntPtr stmt , int index , byte [ ] val , int n , IntPtr free ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_column_count", CallingConvention = CallingConvention.Cdecl)]
public static extern int ColumnCount ( IntPtr stmt ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_column_name", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr ColumnName ( IntPtr stmt , int index ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_column_name16", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr ColumnName16Internal ( IntPtr stmt , int index ) ;
public static string ColumnName16 ( IntPtr stmt , int index )
{
return Marshal . PtrToStringUni ( ColumnName16Internal ( stmt , index ) ) ;
}
[DllImport("sqlite3", EntryPoint = "sqlite3_column_type", CallingConvention = CallingConvention.Cdecl)]
public static extern ColType ColumnType ( IntPtr stmt , int index ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_column_int", CallingConvention = CallingConvention.Cdecl)]
public static extern int ColumnInt ( IntPtr stmt , int index ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_column_int64", CallingConvention = CallingConvention.Cdecl)]
public static extern long ColumnInt64 ( IntPtr stmt , int index ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_column_double", CallingConvention = CallingConvention.Cdecl)]
public static extern double ColumnDouble ( IntPtr stmt , int index ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_column_text", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr ColumnText ( IntPtr stmt , int index ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_column_text16", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr ColumnText16 ( IntPtr stmt , int index ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_column_blob", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr ColumnBlob ( IntPtr stmt , int index ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_column_bytes", CallingConvention = CallingConvention.Cdecl)]
public static extern int ColumnBytes ( IntPtr stmt , int index ) ;
public static string ColumnString ( IntPtr stmt , int index )
{
return Marshal . PtrToStringUni ( SQLite3 . ColumnText16 ( stmt , index ) ) ;
}
public static byte [ ] ColumnByteArray ( IntPtr stmt , int index )
{
int length = ColumnBytes ( stmt , index ) ;
var result = new byte [ length ] ;
if ( length > 0 )
Marshal . Copy ( ColumnBlob ( stmt , index ) , result , 0 , length ) ;
return result ;
}
[DllImport("sqlite3", EntryPoint = "sqlite3_extended_errcode", CallingConvention = CallingConvention.Cdecl)]
public static extern ExtendedResult ExtendedErrCode ( IntPtr db ) ;
[DllImport("sqlite3", EntryPoint = "sqlite3_libversion_number", CallingConvention = CallingConvention.Cdecl)]
public static extern int LibVersionNumber ( ) ;
#else
public static Result Open ( string filename , out Sqlite3DatabaseHandle db )
{
return ( Result ) Sqlite3 . sqlite3_open ( filename , out db ) ;
}
public static Result Open ( string filename , out Sqlite3DatabaseHandle db , int flags , IntPtr zVfs )
{
#if USE_WP8_NATIVE_SQLITE
return ( Result ) Sqlite3 . sqlite3_open_v2 ( filename , out db , flags , "" ) ;
#else
return ( Result ) Sqlite3 . sqlite3_open_v2 ( filename , out db , flags , null ) ;
#endif
}
public static Result Close ( Sqlite3DatabaseHandle db )
{
return ( Result ) Sqlite3 . sqlite3_close ( db ) ;
}
public static Result BusyTimeout ( Sqlite3DatabaseHandle db , int milliseconds )
{
return ( Result ) Sqlite3 . sqlite3_busy_timeout ( db , milliseconds ) ;
}
public static int Changes ( Sqlite3DatabaseHandle db )
{
return Sqlite3 . sqlite3_changes ( db ) ;
}
public static Sqlite3Statement Prepare2 ( Sqlite3DatabaseHandle db , string query )
{
Sqlite3Statement stmt = default ( Sqlite3Statement ) ;
#if USE_WP8_NATIVE_SQLITE
var r = Sqlite3 . sqlite3_prepare_v2 ( db , query , out stmt ) ;
#else
stmt = new Sqlite3Statement ( ) ;
var r = Sqlite3 . sqlite3_prepare_v2 ( db , query , - 1 , ref stmt , 0 ) ;
#endif
if ( r ! = 0 )
{
throw SQLiteException . New ( ( Result ) r , GetErrmsg ( db ) ) ;
}
return stmt ;
}
public static Result Step ( Sqlite3Statement stmt )
{
return ( Result ) Sqlite3 . sqlite3_step ( stmt ) ;
}
public static Result Reset ( Sqlite3Statement stmt )
{
return ( Result ) Sqlite3 . sqlite3_reset ( stmt ) ;
}
public static Result Finalize ( Sqlite3Statement stmt )
{
return ( Result ) Sqlite3 . sqlite3_finalize ( stmt ) ;
}
public static long LastInsertRowid ( Sqlite3DatabaseHandle db )
{
return Sqlite3 . sqlite3_last_insert_rowid ( db ) ;
}
public static string GetErrmsg ( Sqlite3DatabaseHandle db )
{
return Sqlite3 . sqlite3_errmsg ( db ) ;
}
public static int BindParameterIndex ( Sqlite3Statement stmt , string name )
{
return Sqlite3 . sqlite3_bind_parameter_index ( stmt , name ) ;
}
public static int BindNull ( Sqlite3Statement stmt , int index )
{
return Sqlite3 . sqlite3_bind_null ( stmt , index ) ;
}
public static int BindInt ( Sqlite3Statement stmt , int index , int val )
{
return Sqlite3 . sqlite3_bind_int ( stmt , index , val ) ;
}
public static int BindInt64 ( Sqlite3Statement stmt , int index , long val )
{
return Sqlite3 . sqlite3_bind_int64 ( stmt , index , val ) ;
}
public static int BindDouble ( Sqlite3Statement stmt , int index , double val )
{
return Sqlite3 . sqlite3_bind_double ( stmt , index , val ) ;
}
public static int BindText ( Sqlite3Statement stmt , int index , string val , int n , IntPtr free )
{
#if USE_WP8_NATIVE_SQLITE
return Sqlite3 . sqlite3_bind_text ( stmt , index , val , n ) ;
#else
return Sqlite3 . sqlite3_bind_text ( stmt , index , val , n , null ) ;
#endif
}
public static int BindBlob ( Sqlite3Statement stmt , int index , byte [ ] val , int n , IntPtr free )
{
#if USE_WP8_NATIVE_SQLITE
return Sqlite3 . sqlite3_bind_blob ( stmt , index , val , n ) ;
#else
return Sqlite3 . sqlite3_bind_blob ( stmt , index , val , n , null ) ;
#endif
}
public static int ColumnCount ( Sqlite3Statement stmt )
{
return Sqlite3 . sqlite3_column_count ( stmt ) ;
}
public static string ColumnName ( Sqlite3Statement stmt , int index )
{
return Sqlite3 . sqlite3_column_name ( stmt , index ) ;
}
public static string ColumnName16 ( Sqlite3Statement stmt , int index )
{
return Sqlite3 . sqlite3_column_name ( stmt , index ) ;
}
public static ColType ColumnType ( Sqlite3Statement stmt , int index )
{
return ( ColType ) Sqlite3 . sqlite3_column_type ( stmt , index ) ;
}
public static int ColumnInt ( Sqlite3Statement stmt , int index )
{
return Sqlite3 . sqlite3_column_int ( stmt , index ) ;
}
public static long ColumnInt64 ( Sqlite3Statement stmt , int index )
{
return Sqlite3 . sqlite3_column_int64 ( stmt , index ) ;
}
public static double ColumnDouble ( Sqlite3Statement stmt , int index )
{
return Sqlite3 . sqlite3_column_double ( stmt , index ) ;
}
public static string ColumnText ( Sqlite3Statement stmt , int index )
{
return Sqlite3 . sqlite3_column_text ( stmt , index ) ;
}
public static string ColumnText16 ( Sqlite3Statement stmt , int index )
{
return Sqlite3 . sqlite3_column_text ( stmt , index ) ;
}
public static byte [ ] ColumnBlob ( Sqlite3Statement stmt , int index )
{
return Sqlite3 . sqlite3_column_blob ( stmt , index ) ;
}
public static int ColumnBytes ( Sqlite3Statement stmt , int index )
{
return Sqlite3 . sqlite3_column_bytes ( stmt , index ) ;
}
public static string ColumnString ( Sqlite3Statement stmt , int index )
{
return Sqlite3 . sqlite3_column_text ( stmt , index ) ;
}
public static byte [ ] ColumnByteArray ( Sqlite3Statement stmt , int index )
{
return ColumnBlob ( stmt , index ) ;
}
public static Result EnableLoadExtension ( Sqlite3DatabaseHandle db , int onoff )
{
return ( Result ) Sqlite3 . sqlite3_enable_load_extension ( db , onoff ) ;
}
public static ExtendedResult ExtendedErrCode ( Sqlite3DatabaseHandle db )
{
return ( ExtendedResult ) Sqlite3 . sqlite3_extended_errcode ( db ) ;
}
#endif
public enum ColType : int
{
Integer = 1 ,
Float = 2 ,
Text = 3 ,
Blob = 4 ,
Null = 5
}
}
}