2008-07-01 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / Mono.Data.Sqlite / Mono.Data.Sqlite / SqliteConnection.cs
1 //
2 // Mono.Data.Sqlite.SqliteConnection.cs
3 //
4 // Represents an open connection to a Sqlite database file.
5 //
6 // Author(s): Vladimir Vukicevic  <vladimir@pobox.com>
7 //            Everaldo Canuto  <everaldo_canuto@yahoo.com.br>
8 //
9 // Copyright (C) 2002  Vladimir Vukicevic
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 #if !NET_2_0
31 using System;
32 using System.Runtime.InteropServices;
33 using System.Data;
34 using System.IO;
35 using System.Text;
36
37 namespace Mono.Data.Sqlite
38 {
39         public class SqliteConnection : IDbConnection, ICloneable
40         {
41
42 #region Fields
43                 
44                 private string conn_str;
45                 private string db_file;
46                 private int db_mode;
47                 private int db_version;
48                 private IntPtr sqlite_handle;
49                 private ConnectionState state;
50                 private Encoding encoding;
51                 private int busy_timeout;
52
53 #endregion
54
55 #region Constructors and destructors
56                 
57                 public SqliteConnection ()
58                 {
59                         db_file = null;
60                         db_mode = 0644;
61                         db_version = 2;
62                         state = ConnectionState.Closed;
63                         sqlite_handle = IntPtr.Zero;
64                         encoding = null;
65                         busy_timeout = 0;
66                 }
67                 
68                 public SqliteConnection (string connstring) : this ()
69                 {
70                         ConnectionString = connstring;
71                 }
72
73                 public void Dispose ()
74                 {
75                         Close ();
76                 }
77                                 
78 #endregion
79
80 #region Properties
81
82                 public string ConnectionString {
83                         get { return conn_str; }
84                         set { SetConnectionString(value); }
85                 }               
86
87                 public int ConnectionTimeout {
88                         get { return 0; }
89                 }       
90
91                 public string Database {
92                         get { return db_file; }
93                 }               
94
95                 public ConnectionState State {
96                         get { return state; }
97                 }
98                 
99                 public Encoding Encoding {
100                         get { return encoding; }
101                 }
102
103                 public int Version {
104                         get { return db_version; }
105                 }
106
107                 internal IntPtr Handle {
108                         get { return sqlite_handle; }
109                 }               
110
111                 public int LastInsertRowId {
112                         get {
113                                 if (Version == 3)
114                                         return (int)Sqlite.sqlite3_last_insert_rowid (Handle);
115                                 else
116                                         return Sqlite.sqlite_last_insert_rowid (Handle);
117                         }
118                 }
119
120                 public int BusyTimeout {
121                         get {
122                                 return busy_timeout;  
123                         }
124                         set {
125                                 busy_timeout = value < 0 ? 0 : value;
126                         }
127                 }
128                 
129 #endregion
130
131 #region Private Methods
132                 
133                 private void SetConnectionString(string connstring)
134                 {
135                         if (connstring == null) {
136                                 Close ();
137                                 conn_str = null;
138                                 return;
139                         }
140                         
141                         if (connstring != conn_str) {
142                                 Close ();
143                                 conn_str = connstring;
144                                 
145                                 db_file = null;
146                                 db_mode = 0644;
147                                 
148                                 string[] conn_pieces = connstring.Split (',');
149                                 for (int i = 0; i < conn_pieces.Length; i++) {
150                                         string piece = conn_pieces [i].Trim ();
151                                         if (piece.Length == 0) { // ignore empty elements
152                                                 continue;
153                                         }
154                                         string[] arg_pieces = piece.Split ('=');
155                                         if (arg_pieces.Length != 2) {
156                                                 throw new InvalidOperationException ("Invalid connection string");
157                                         }
158                                         string token = arg_pieces[0].ToLower (System.Globalization.CultureInfo.InvariantCulture).Trim ();
159                                         string tvalue = arg_pieces[1].Trim ();
160                                         string tvalue_lc = arg_pieces[1].ToLower (System.Globalization.CultureInfo.InvariantCulture).Trim ();
161                                         switch (token) {
162                                                 case "uri": 
163                                                         if (tvalue_lc.StartsWith ("file://")) {
164                                                                 db_file = tvalue.Substring (7);
165                                                         } else if (tvalue_lc.StartsWith ("file:")) {
166                                                                 db_file = tvalue.Substring (5);
167                                                         } else if (tvalue_lc.StartsWith ("/")) {
168                                                                 db_file = tvalue;
169                                                         } else {
170                                                                 throw new InvalidOperationException ("Invalid connection string: invalid URI");
171                                                         }
172                                                         break;
173
174                                                 case "mode": 
175                                                         db_mode = Convert.ToInt32 (tvalue);
176                                                         break;
177
178                                                 case "version":
179                                                         db_version = Convert.ToInt32 (tvalue);
180                                                         break;
181
182                                                 case "encoding": // only for sqlite2
183                                                         encoding = Encoding.GetEncoding (tvalue);
184                                                         break;
185
186                                                 case "busy_timeout":
187                                                         busy_timeout = Convert.ToInt32 (tvalue);
188                                                         break;
189                                         }
190                                 }
191                                 
192                                 if (db_file == null) {
193                                         throw new InvalidOperationException ("Invalid connection string: no URI");
194                                 }
195                         }
196                 }               
197 #endregion
198
199 #region Internal Methods
200                 
201                 internal void StartExec ()
202                 {
203                         // use a mutex here
204                         state = ConnectionState.Executing;
205                 }
206                 
207                 internal void EndExec ()
208                 {
209                         state = ConnectionState.Open;
210                 }
211                 
212 #endregion
213
214 #region Public Methods
215
216                 object ICloneable.Clone ()
217                 {
218                         return new SqliteConnection (ConnectionString);
219                 }
220                 
221
222                 public IDbTransaction BeginTransaction ()
223                 {
224                         if (state != ConnectionState.Open)
225                                 throw new InvalidOperationException("Invalid operation: The connection is closed");
226                         
227                         SqliteTransaction t = new SqliteTransaction();
228                         t.Connection = this;
229                         SqliteCommand cmd = (SqliteCommand)this.CreateCommand();
230                         cmd.CommandText = "BEGIN";
231                         cmd.ExecuteNonQuery();
232                         return t;
233                 }
234
235                 public IDbTransaction BeginTransaction (IsolationLevel il)
236                 {
237                         throw new InvalidOperationException();
238                 }               
239
240                 public void Close ()
241                 {
242                         if (state != ConnectionState.Open) {
243                                 return;
244                         }
245                         
246                         state = ConnectionState.Closed;
247                 
248                         if (Version == 3)
249                                 Sqlite.sqlite3_close (sqlite_handle);
250                         else 
251                                 Sqlite.sqlite_close (sqlite_handle);
252                         sqlite_handle = IntPtr.Zero;
253                 }               
254
255                 public void ChangeDatabase (string databaseName)
256                 {
257                         Close ();
258                         db_file = databaseName;
259                         Open ();
260                 }
261                 
262                 IDbCommand IDbConnection.CreateCommand ()
263                 {
264                         return CreateCommand ();
265                 }
266
267                 public SqliteCommand CreateCommand ()
268                 {
269                         return new SqliteCommand (null, this);
270                 }               
271
272                 public void Open ()
273                 {
274                         if (conn_str == null) {
275                                 throw new InvalidOperationException ("No database specified");
276                         }
277                         
278                         if (state != ConnectionState.Closed) {
279                                 return;
280                         }
281                         
282                         IntPtr errmsg = IntPtr.Zero;
283
284                         if (Version == 2){
285                                 try {
286                                         sqlite_handle = Sqlite.sqlite_open(db_file, db_mode, out errmsg);
287                                         if (errmsg != IntPtr.Zero) {
288                                                 string msg = Marshal.PtrToStringAnsi (errmsg);
289                                                 Sqlite.sqliteFree (errmsg);
290                                                 throw new ApplicationException (msg);
291                                         }
292                                 } catch (DllNotFoundException) {
293                                         db_version = 3;
294                                 } catch (EntryPointNotFoundException) {
295                                         db_version = 3;
296                                 }
297                                 
298                                 if (busy_timeout != 0)
299                                         Sqlite.sqlite_busy_timeout (sqlite_handle, busy_timeout);
300                         }
301                         if (Version == 3) {
302                                 int err = Sqlite.sqlite3_open16(db_file, out sqlite_handle);
303                                 if (err == (int)SqliteError.ERROR)
304                                         throw new ApplicationException (Marshal.PtrToStringUni( Sqlite.sqlite3_errmsg16 (sqlite_handle)));
305                                 if (busy_timeout != 0)
306                                         Sqlite.sqlite3_busy_timeout (sqlite_handle, busy_timeout);
307                         } else {
308                         }
309                         state = ConnectionState.Open;
310                 }
311 #endregion
312         }
313 }
314 #endif