com.backnd.database 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Attributes/ColumnAttribute.cs +46 -0
- package/Attributes/ColumnAttribute.cs.meta +11 -0
- package/Attributes/PrimaryKeyAttribute.cs +12 -0
- package/Attributes/PrimaryKeyAttribute.cs.meta +11 -0
- package/Attributes/TableAttribute.cs +44 -0
- package/Attributes/TableAttribute.cs.meta +11 -0
- package/Attributes.meta +8 -0
- package/BACKND.Database.asmdef +14 -0
- package/BACKND.Database.asmdef.meta +7 -0
- package/BaseModel.cs +22 -0
- package/BaseModel.cs.meta +11 -0
- package/Client.cs +490 -0
- package/Client.cs.meta +11 -0
- package/Editor/BACKND.Database.Editor.asmdef +18 -0
- package/Editor/BACKND.Database.Editor.asmdef.meta +7 -0
- package/Editor/PackageAssetInstaller.cs +251 -0
- package/Editor/PackageAssetInstaller.cs.meta +11 -0
- package/Editor.meta +8 -0
- package/Exceptions/DatabaseException.cs +34 -0
- package/Exceptions/DatabaseException.cs.meta +11 -0
- package/Exceptions.meta +8 -0
- package/Internal/ExpressionAnalyzer.cs +433 -0
- package/Internal/ExpressionAnalyzer.cs.meta +11 -0
- package/Internal/QueryTypes.cs +61 -0
- package/Internal/QueryTypes.cs.meta +11 -0
- package/Internal/SqlBuilder.cs +375 -0
- package/Internal/SqlBuilder.cs.meta +11 -0
- package/Internal/ValueFormatter.cs +103 -0
- package/Internal/ValueFormatter.cs.meta +11 -0
- package/Internal.meta +8 -0
- package/Network/DatabaseExecutor.cs +171 -0
- package/Network/DatabaseExecutor.cs.meta +11 -0
- package/Network/DatabaseRequest.cs +16 -0
- package/Network/DatabaseRequest.cs.meta +11 -0
- package/Network/DatabaseResponse.cs +81 -0
- package/Network/DatabaseResponse.cs.meta +11 -0
- package/Network.meta +8 -0
- package/QueryBuilder.cs +1001 -0
- package/QueryBuilder.cs.meta +11 -0
- package/README.md +24 -0
- package/TheBackend~/Plugins/Android/Backend.aar +0 -0
- package/TheBackend~/Plugins/Backend.dll +0 -0
- package/TheBackend~/Plugins/Editor/TheBackendMultiSettingEditor.dll +0 -0
- package/TheBackend~/Plugins/Editor/TheBackendSettingEditor.dll +0 -0
- package/TheBackend~/Plugins/LitJSON.dll +0 -0
- package/TheBackend~/Plugins/Settings/TheBackendHashKeySettings.dll +0 -0
- package/TheBackend~/Plugins/Settings/TheBackendMultiSettings.dll +0 -0
- package/TheBackend~/Plugins/Settings/TheBackendSettings.dll +0 -0
- package/Tools/BTask.cs +905 -0
- package/Tools/BTask.cs.meta +11 -0
- package/Tools/DatabaseLoop.cs +110 -0
- package/Tools/DatabaseLoop.cs.meta +11 -0
- package/Tools/JsonHelper.cs +82 -0
- package/Tools/JsonHelper.cs.meta +11 -0
- package/Tools.meta +8 -0
- package/TransactionBuilder.cs +154 -0
- package/TransactionBuilder.cs.meta +11 -0
- package/TransactionQueryBuilder.cs +205 -0
- package/TransactionQueryBuilder.cs.meta +11 -0
- package/TransactionResult.cs +33 -0
- package/TransactionResult.cs.meta +11 -0
- package/package.json +20 -0
- package/package.json.meta +7 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
|
|
3
|
+
namespace BACKND.Database
|
|
4
|
+
{
|
|
5
|
+
public enum DatabaseType
|
|
6
|
+
{
|
|
7
|
+
None,
|
|
8
|
+
Int32,
|
|
9
|
+
UInt32,
|
|
10
|
+
Int64,
|
|
11
|
+
UInt64,
|
|
12
|
+
Float,
|
|
13
|
+
Bool,
|
|
14
|
+
String,
|
|
15
|
+
DateTime,
|
|
16
|
+
UUID,
|
|
17
|
+
Json
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
|
21
|
+
public sealed class ColumnAttribute : Attribute
|
|
22
|
+
{
|
|
23
|
+
public string ColumnName { get; } = string.Empty;
|
|
24
|
+
public DatabaseType DataType { get; } = DatabaseType.None;
|
|
25
|
+
public bool NotNull { get; set; } = false;
|
|
26
|
+
public string DefaultValue { get; set; } = string.Empty;
|
|
27
|
+
|
|
28
|
+
public ColumnAttribute() { }
|
|
29
|
+
|
|
30
|
+
public ColumnAttribute(string columnName)
|
|
31
|
+
{
|
|
32
|
+
ColumnName = columnName;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public ColumnAttribute(DatabaseType dataType)
|
|
36
|
+
{
|
|
37
|
+
DataType = dataType;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public ColumnAttribute(string columnName, DatabaseType dataType)
|
|
41
|
+
{
|
|
42
|
+
ColumnName = columnName;
|
|
43
|
+
DataType = dataType;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
|
|
3
|
+
namespace BACKND.Database
|
|
4
|
+
{
|
|
5
|
+
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
|
6
|
+
public class PrimaryKeyAttribute : Attribute
|
|
7
|
+
{
|
|
8
|
+
public bool AutoIncrement { get; set; } = false;
|
|
9
|
+
|
|
10
|
+
public PrimaryKeyAttribute() { }
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
|
|
3
|
+
namespace BACKND.Database
|
|
4
|
+
{
|
|
5
|
+
public enum TableType
|
|
6
|
+
{
|
|
7
|
+
UserTable = 1,
|
|
8
|
+
FlexibleTable = 2
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public enum TablePermission
|
|
12
|
+
{
|
|
13
|
+
SELF,
|
|
14
|
+
OTHERS
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
|
18
|
+
public class TableAttribute : Attribute
|
|
19
|
+
{
|
|
20
|
+
public string TableName { get; } = string.Empty;
|
|
21
|
+
public TableType TableType { get; } = TableType.FlexibleTable;
|
|
22
|
+
public bool ClientAccess { get; set; } = true;
|
|
23
|
+
public TablePermission[] ReadPermissions { get; set; } = { TablePermission.SELF, TablePermission.OTHERS };
|
|
24
|
+
public TablePermission[] WritePermissions { get; set; } = { TablePermission.SELF, TablePermission.OTHERS };
|
|
25
|
+
|
|
26
|
+
public TableAttribute() { }
|
|
27
|
+
|
|
28
|
+
public TableAttribute(string tableName)
|
|
29
|
+
{
|
|
30
|
+
TableName = tableName;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public TableAttribute(TableType tableType)
|
|
34
|
+
{
|
|
35
|
+
TableType = tableType;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public TableAttribute(string tableName, TableType tableType)
|
|
39
|
+
{
|
|
40
|
+
TableName = tableName;
|
|
41
|
+
TableType = tableType;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
package/Attributes.meta
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "BACKND.Database",
|
|
3
|
+
"rootNamespace": "BACKND.Database",
|
|
4
|
+
"references": [],
|
|
5
|
+
"includePlatforms": [],
|
|
6
|
+
"excludePlatforms": [],
|
|
7
|
+
"allowUnsafeCode": false,
|
|
8
|
+
"overrideReferences": false,
|
|
9
|
+
"precompiledReferences": [],
|
|
10
|
+
"autoReferenced": true,
|
|
11
|
+
"defineConstraints": [],
|
|
12
|
+
"versionDefines": [],
|
|
13
|
+
"noEngineReferences": false
|
|
14
|
+
}
|
package/BaseModel.cs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
namespace BACKND.Database
|
|
2
|
+
{
|
|
3
|
+
public abstract class BaseModel
|
|
4
|
+
{
|
|
5
|
+
public virtual string GetTableName() { return string.Empty; }
|
|
6
|
+
public virtual TableType GetTableType() { return TableType.FlexibleTable; }
|
|
7
|
+
public virtual bool GetClientAccess() { return false; }
|
|
8
|
+
public virtual string[] GetReadPermissions() { return new string[] { "SELF", "OTHERS" }; }
|
|
9
|
+
public virtual string[] GetWritePermissions() { return new string[] { "SELF", "OTHERS" }; }
|
|
10
|
+
public virtual string[] GetPrimaryKeyColumnNames() { return new string[0]; }
|
|
11
|
+
public virtual string GetAutoIncrementColumnName() { return string.Empty; }
|
|
12
|
+
public virtual string GetPrimaryKey() { return string.Empty; }
|
|
13
|
+
public virtual string GetColumnList() { return string.Empty; }
|
|
14
|
+
public virtual string GetColumnDataType(string columnName) { return string.Empty; }
|
|
15
|
+
public virtual bool IsColumnNullable(string columnName) { return false; }
|
|
16
|
+
public virtual bool IsPropertyNullableType(string columnName) { return false; }
|
|
17
|
+
public virtual string GetColumnDefaultValue(string columnName) { return string.Empty; }
|
|
18
|
+
public virtual string GetColumnName(string propertyName) { return string.Empty; }
|
|
19
|
+
public virtual object GetValue(string columnName) { return null; }
|
|
20
|
+
public virtual void SetValue(string columnName, object value) { }
|
|
21
|
+
}
|
|
22
|
+
}
|
package/Client.cs
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.Linq;
|
|
4
|
+
using System.Text;
|
|
5
|
+
using System.Threading;
|
|
6
|
+
|
|
7
|
+
using BACKND.Database.Exceptions;
|
|
8
|
+
using BACKND.Database.Network;
|
|
9
|
+
|
|
10
|
+
namespace BACKND.Database
|
|
11
|
+
{
|
|
12
|
+
public class Client
|
|
13
|
+
{
|
|
14
|
+
public string UserUUID => headers.ContainsKey("x-gamerid") ? headers["x-gamerid"] : null;
|
|
15
|
+
|
|
16
|
+
private bool initialized = false;
|
|
17
|
+
|
|
18
|
+
private readonly Dictionary<string, string> headers = new Dictionary<string, string>();
|
|
19
|
+
|
|
20
|
+
private readonly Dictionary<Type, bool> createdTables = new Dictionary<Type, bool>();
|
|
21
|
+
|
|
22
|
+
private readonly Queue<QueuedRequest> requestQueue = new Queue<QueuedRequest>();
|
|
23
|
+
private readonly Queue<QueuedRequest> highPriorityQueue = new Queue<QueuedRequest>();
|
|
24
|
+
private readonly object queueLock = new object();
|
|
25
|
+
private bool isProcessingQueue = false;
|
|
26
|
+
private readonly CancellationTokenSource queueCancellationSource;
|
|
27
|
+
private readonly float requestDelay = 0.1f;
|
|
28
|
+
|
|
29
|
+
public Client(string uuid)
|
|
30
|
+
{
|
|
31
|
+
headers["database_uuid"] = uuid;
|
|
32
|
+
|
|
33
|
+
this.queueCancellationSource = new CancellationTokenSource();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public async BTask Initialize()
|
|
37
|
+
{
|
|
38
|
+
if (initialized)
|
|
39
|
+
{
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
var userInfoResult = await GetUserInfoAsync();
|
|
44
|
+
if (!userInfoResult.IsSuccess())
|
|
45
|
+
{
|
|
46
|
+
UnityEngine.Debug.LogError("Failed to Backnd Service Initialization or Login Process - " + userInfoResult.ToString());
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
var json = Newtonsoft.Json.Linq.JObject.Parse(userInfoResult.ReturnValue);
|
|
51
|
+
headers["x-gamerid"] = json["row"]["gamerId"].ToString();
|
|
52
|
+
|
|
53
|
+
var settings = BackEnd.Backend.GetBackndChatSettings();
|
|
54
|
+
foreach (var header in settings)
|
|
55
|
+
{
|
|
56
|
+
headers[header.Key] = header.Value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//headers["x-gamerid"] = "40a1f3f0-19a1-11f0-9ee9-c1c83fc196eb";
|
|
60
|
+
|
|
61
|
+
StartQueueProcessing();
|
|
62
|
+
|
|
63
|
+
initialized = true;
|
|
64
|
+
|
|
65
|
+
await BTask.CompletedTask;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private BTask<BackEnd.BackendReturnObject> GetUserInfoAsync()
|
|
69
|
+
{
|
|
70
|
+
var tcs = new BTaskCompletionSource<BackEnd.BackendReturnObject>();
|
|
71
|
+
|
|
72
|
+
BackEnd.Backend.BMember.GetUserInfoV2(result =>
|
|
73
|
+
{
|
|
74
|
+
tcs.SetResult(result);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return tcs.Task;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#region Queue Management
|
|
81
|
+
|
|
82
|
+
private void StartQueueProcessing()
|
|
83
|
+
{
|
|
84
|
+
if (isProcessingQueue) return;
|
|
85
|
+
|
|
86
|
+
isProcessingQueue = true;
|
|
87
|
+
ProcessQueue();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private async void ProcessQueue()
|
|
91
|
+
{
|
|
92
|
+
while (!queueCancellationSource.Token.IsCancellationRequested)
|
|
93
|
+
{
|
|
94
|
+
QueuedRequest request = null;
|
|
95
|
+
|
|
96
|
+
lock (queueLock)
|
|
97
|
+
{
|
|
98
|
+
if (highPriorityQueue.Count > 0)
|
|
99
|
+
{
|
|
100
|
+
request = highPriorityQueue.Dequeue();
|
|
101
|
+
}
|
|
102
|
+
else if (requestQueue.Count > 0)
|
|
103
|
+
{
|
|
104
|
+
request = requestQueue.Dequeue();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (request != null)
|
|
109
|
+
{
|
|
110
|
+
await ProcessRequest(request);
|
|
111
|
+
|
|
112
|
+
if (requestDelay > 0)
|
|
113
|
+
{
|
|
114
|
+
await BTask.Delay(requestDelay);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else
|
|
118
|
+
{
|
|
119
|
+
await BTask.Yield();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
isProcessingQueue = false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private async BTask ProcessRequest(QueuedRequest queuedRequest)
|
|
127
|
+
{
|
|
128
|
+
try
|
|
129
|
+
{
|
|
130
|
+
var response = await DatabaseExecutor.Execute(queuedRequest.Request, headers, queuedRequest.CancellationToken);
|
|
131
|
+
queuedRequest.TaskCompletionSource.SetResult(response);
|
|
132
|
+
}
|
|
133
|
+
catch (Exception ex)
|
|
134
|
+
{
|
|
135
|
+
queuedRequest.TaskCompletionSource.SetException(ex);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private BTask<Response> EnqueueRequest(DatabaseRequest request, bool highPriority = false, CancellationToken cancellationToken = default)
|
|
140
|
+
{
|
|
141
|
+
if (!initialized)
|
|
142
|
+
{
|
|
143
|
+
throw new InvalidOperationException("[Database.Client] Client not initialized. Call Initialize() first.");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
var queuedRequest = new QueuedRequest
|
|
147
|
+
{
|
|
148
|
+
Request = request,
|
|
149
|
+
TaskCompletionSource = new BTaskCompletionSource<Response>(),
|
|
150
|
+
CancellationToken = cancellationToken,
|
|
151
|
+
Timestamp = DateTime.Now
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
lock (queueLock)
|
|
155
|
+
{
|
|
156
|
+
if (highPriority)
|
|
157
|
+
{
|
|
158
|
+
highPriorityQueue.Enqueue(queuedRequest);
|
|
159
|
+
}
|
|
160
|
+
else
|
|
161
|
+
{
|
|
162
|
+
requestQueue.Enqueue(queuedRequest);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return queuedRequest.TaskCompletionSource.Task;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#endregion
|
|
170
|
+
|
|
171
|
+
#region Table Operations
|
|
172
|
+
|
|
173
|
+
public async BTask CreateTable<T>() where T : BaseModel, new()
|
|
174
|
+
{
|
|
175
|
+
var type = typeof(T);
|
|
176
|
+
if (createdTables.ContainsKey(type))
|
|
177
|
+
{
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
var query = BuildCreateTableQuery<T>();
|
|
182
|
+
|
|
183
|
+
var request = new DatabaseRequest { Query = query };
|
|
184
|
+
|
|
185
|
+
var response = await EnqueueRequest(request, highPriority: true);
|
|
186
|
+
|
|
187
|
+
if (response.Success)
|
|
188
|
+
{
|
|
189
|
+
createdTables[type] = true;
|
|
190
|
+
}
|
|
191
|
+
else
|
|
192
|
+
{
|
|
193
|
+
throw new DatabaseException($"Failed to create table: {response.Error}");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public async BTask DropTable<T>() where T : BaseModel, new()
|
|
198
|
+
{
|
|
199
|
+
var type = typeof(T);
|
|
200
|
+
var instance = new T();
|
|
201
|
+
var query = $"DROP TABLE {instance.GetTableName()}";
|
|
202
|
+
|
|
203
|
+
var request = new DatabaseRequest { Query = query };
|
|
204
|
+
|
|
205
|
+
var response = await EnqueueRequest(request, highPriority: true);
|
|
206
|
+
|
|
207
|
+
if (response.Success)
|
|
208
|
+
{
|
|
209
|
+
createdTables.Remove(type);
|
|
210
|
+
}
|
|
211
|
+
else
|
|
212
|
+
{
|
|
213
|
+
throw new DatabaseException($"Failed to drop table: {response.Error}");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
public async BTask AlterTable<T>(string alterStatement) where T : BaseModel, new()
|
|
218
|
+
{
|
|
219
|
+
var instance = new T();
|
|
220
|
+
var query = $"ALTER TABLE {instance.GetTableName()} {alterStatement}";
|
|
221
|
+
|
|
222
|
+
var request = new DatabaseRequest { Query = query };
|
|
223
|
+
|
|
224
|
+
var response = await EnqueueRequest(request, highPriority: true);
|
|
225
|
+
|
|
226
|
+
if (!response.Success)
|
|
227
|
+
{
|
|
228
|
+
throw new DatabaseException($"Failed to alter table: {response.Error}");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#endregion
|
|
233
|
+
|
|
234
|
+
#region Index Operations
|
|
235
|
+
|
|
236
|
+
public async BTask CreateIndex<T>(string indexName, params string[] columns) where T : BaseModel, new()
|
|
237
|
+
{
|
|
238
|
+
var instance = new T();
|
|
239
|
+
var query = $"CREATE INDEX {indexName} ON {instance.GetTableName()} ({string.Join(", ", columns)})";
|
|
240
|
+
|
|
241
|
+
var request = new DatabaseRequest { Query = query };
|
|
242
|
+
|
|
243
|
+
var response = await EnqueueRequest(request, highPriority: true);
|
|
244
|
+
|
|
245
|
+
if (!response.Success)
|
|
246
|
+
{
|
|
247
|
+
throw new DatabaseException($"Failed to create index: {response.Error}");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public async BTask DropIndex<T>(string indexName) where T : BaseModel, new()
|
|
252
|
+
{
|
|
253
|
+
var instance = new T();
|
|
254
|
+
var query = $"DROP INDEX {indexName} ON {instance.GetTableName()}";
|
|
255
|
+
|
|
256
|
+
var request = new DatabaseRequest { Query = query };
|
|
257
|
+
|
|
258
|
+
var response = await EnqueueRequest(request, highPriority: true);
|
|
259
|
+
|
|
260
|
+
if (!response.Success)
|
|
261
|
+
{
|
|
262
|
+
throw new DatabaseException($"Failed to drop index: {response.Error}");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
#endregion
|
|
267
|
+
|
|
268
|
+
#region Query Operations
|
|
269
|
+
|
|
270
|
+
public QueryBuilder<T> From<T>() where T : BaseModel, new()
|
|
271
|
+
{
|
|
272
|
+
if (!initialized)
|
|
273
|
+
{
|
|
274
|
+
throw new InvalidOperationException("[Database.Client] Client not initialized. Call Initialize() first.");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return new QueryBuilder<T>(this);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
public QueryBuilder<T> Query<T>() where T : BaseModel, new()
|
|
281
|
+
{
|
|
282
|
+
if (!initialized)
|
|
283
|
+
{
|
|
284
|
+
throw new InvalidOperationException("[Database.Client] Client not initialized. Call Initialize() first.");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return new QueryBuilder<T>(this);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
public async BTask<Response> ExecuteRawQuery(string query, CancellationToken cancellationToken = default)
|
|
291
|
+
{
|
|
292
|
+
var request = new DatabaseRequest { Query = query };
|
|
293
|
+
return await EnqueueRequest(request, highPriority: false, cancellationToken);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
internal async BTask<Response> ExecuteQuery(DatabaseRequest request, CancellationToken cancellationToken = default)
|
|
297
|
+
{
|
|
298
|
+
return await EnqueueRequest(request, highPriority: false, cancellationToken);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
internal async BTask<Response> ExecuteMutation(DatabaseRequest request, CancellationToken cancellationToken = default)
|
|
302
|
+
{
|
|
303
|
+
return await EnqueueRequest(request, highPriority: true, cancellationToken);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/// <summary>
|
|
307
|
+
/// 트랜잭션 빌더 생성
|
|
308
|
+
/// </summary>
|
|
309
|
+
public TransactionBuilder Transaction()
|
|
310
|
+
{
|
|
311
|
+
if (!initialized)
|
|
312
|
+
{
|
|
313
|
+
throw new InvalidOperationException("[Database.Client] Client not initialized. Call Initialize() first.");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return new TransactionBuilder(this);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/// <summary>
|
|
320
|
+
/// 트랜잭션 실행 (내부용)
|
|
321
|
+
/// </summary>
|
|
322
|
+
internal async BTask<Response> ExecuteTransaction(DatabaseRequest request, CancellationToken cancellationToken = default)
|
|
323
|
+
{
|
|
324
|
+
return await EnqueueRequest(request, highPriority: true, cancellationToken);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#endregion
|
|
328
|
+
|
|
329
|
+
#region Cleanup
|
|
330
|
+
|
|
331
|
+
public void Dispose()
|
|
332
|
+
{
|
|
333
|
+
queueCancellationSource?.Cancel();
|
|
334
|
+
queueCancellationSource?.Dispose();
|
|
335
|
+
|
|
336
|
+
lock (queueLock)
|
|
337
|
+
{
|
|
338
|
+
requestQueue.Clear();
|
|
339
|
+
highPriorityQueue.Clear();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
initialized = false;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
#endregion
|
|
346
|
+
|
|
347
|
+
#region Query Generation Helper Methods
|
|
348
|
+
|
|
349
|
+
private string BuildCreateTableQuery<T>() where T : BaseModel, new()
|
|
350
|
+
{
|
|
351
|
+
var instance = new T();
|
|
352
|
+
var tableName = instance.GetTableName();
|
|
353
|
+
var columnList = instance.GetColumnList();
|
|
354
|
+
|
|
355
|
+
if (string.IsNullOrEmpty(tableName) || string.IsNullOrEmpty(columnList))
|
|
356
|
+
{
|
|
357
|
+
throw new InvalidOperationException($"Model class {typeof(T).Name} has not been processed by DatabaseWeaver. Table name or column information is missing.");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
var sb = new StringBuilder();
|
|
361
|
+
sb.Append($"CREATE TABLE {tableName} (");
|
|
362
|
+
|
|
363
|
+
var columns = columnList.Split(',').Select(c => c.Trim()).ToArray();
|
|
364
|
+
var columnDefs = new List<string>();
|
|
365
|
+
|
|
366
|
+
foreach (var column in columns)
|
|
367
|
+
{
|
|
368
|
+
var dataType = instance.GetColumnDataType(column);
|
|
369
|
+
var nullable = instance.IsColumnNullable(column);
|
|
370
|
+
var defaultValue = instance.GetColumnDefaultValue(column);
|
|
371
|
+
var isPrimary = instance.GetPrimaryKeyColumnNames().Any(pk => pk.Equals(column, StringComparison.OrdinalIgnoreCase));
|
|
372
|
+
|
|
373
|
+
var columnDef = $"{column} {dataType}";
|
|
374
|
+
|
|
375
|
+
if (isPrimary)
|
|
376
|
+
{
|
|
377
|
+
columnDef += " PRIMARY KEY";
|
|
378
|
+
if (column.Equals(instance.GetAutoIncrementColumnName(), StringComparison.OrdinalIgnoreCase))
|
|
379
|
+
{
|
|
380
|
+
columnDef += " AUTO_INCREMENT";
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
else if (!nullable)
|
|
384
|
+
{
|
|
385
|
+
columnDef += " NOT NULL";
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (!string.IsNullOrEmpty(defaultValue) && !isPrimary)
|
|
389
|
+
{
|
|
390
|
+
columnDef += $" DEFAULT {FormatDefaultValue(defaultValue, dataType)}";
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
columnDefs.Add(columnDef);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
sb.Append(string.Join(", ", columnDefs));
|
|
397
|
+
|
|
398
|
+
var tableType = instance.GetTableType();
|
|
399
|
+
var tableTypeClause = tableType == TableType.UserTable ? "USERTABLE" : "FLEXIBLETABLE";
|
|
400
|
+
var readPermissions = string.Join(", ", instance.GetReadPermissions());
|
|
401
|
+
var writePermissions = string.Join(", ", instance.GetWritePermissions());
|
|
402
|
+
|
|
403
|
+
sb.Append($", {tableTypeClause} (");
|
|
404
|
+
sb.Append($"CLIENT_ACCESS = {instance.GetClientAccess().ToString().ToLower()}, ");
|
|
405
|
+
sb.Append($"READ = ({readPermissions}), ");
|
|
406
|
+
sb.Append($"WRITE = ({writePermissions})");
|
|
407
|
+
sb.Append("))");
|
|
408
|
+
|
|
409
|
+
return sb.ToString();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
private static readonly HashSet<string> SqlFunctionsAndKeywords = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
413
|
+
{
|
|
414
|
+
"NOW()", "CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP()", "UUID()",
|
|
415
|
+
"CURRENT_DATE", "CURRENT_DATE()", "CURRENT_TIME", "CURRENT_TIME()",
|
|
416
|
+
"NULL"
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
private static string FormatDefaultValue(string defaultValue, string dataType)
|
|
420
|
+
{
|
|
421
|
+
if (string.IsNullOrWhiteSpace(defaultValue))
|
|
422
|
+
return defaultValue;
|
|
423
|
+
|
|
424
|
+
var trimmed = defaultValue.Trim();
|
|
425
|
+
|
|
426
|
+
// SQL 함수 및 키워드(NULL)는 그대로 반환
|
|
427
|
+
if (SqlFunctionsAndKeywords.Contains(trimmed))
|
|
428
|
+
return trimmed;
|
|
429
|
+
|
|
430
|
+
// 이미 따옴표로 감싸져 있으면 그대로 반환
|
|
431
|
+
if ((trimmed.StartsWith("'") && trimmed.EndsWith("'")) ||
|
|
432
|
+
(trimmed.StartsWith("\"") && trimmed.EndsWith("\"")))
|
|
433
|
+
{
|
|
434
|
+
return trimmed;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// 함수와 NULL을 제외한 모든 값은 따옴표로 감싸기
|
|
438
|
+
return $"'{trimmed.Replace("'", "''")}'";
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
#endregion
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
internal class QueuedRequest
|
|
445
|
+
{
|
|
446
|
+
public DatabaseRequest Request { get; set; }
|
|
447
|
+
public BTaskCompletionSource<Response> TaskCompletionSource { get; set; }
|
|
448
|
+
public CancellationToken CancellationToken { get; set; }
|
|
449
|
+
public DateTime Timestamp { get; set; }
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
public class BTaskCompletionSource<T>
|
|
453
|
+
{
|
|
454
|
+
private BTask<T> task;
|
|
455
|
+
private bool completed = false;
|
|
456
|
+
private readonly object lockObj = new object();
|
|
457
|
+
|
|
458
|
+
public BTaskCompletionSource()
|
|
459
|
+
{
|
|
460
|
+
task = new BTask<T>();
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
public BTask<T> Task => task;
|
|
464
|
+
|
|
465
|
+
public void SetResult(T result)
|
|
466
|
+
{
|
|
467
|
+
lock (lockObj)
|
|
468
|
+
{
|
|
469
|
+
if (completed) return;
|
|
470
|
+
completed = true;
|
|
471
|
+
task.SetResult(result);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
public void SetException(Exception exception)
|
|
476
|
+
{
|
|
477
|
+
lock (lockObj)
|
|
478
|
+
{
|
|
479
|
+
if (completed) return;
|
|
480
|
+
completed = true;
|
|
481
|
+
task.SetException(exception);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
public void SetCanceled()
|
|
486
|
+
{
|
|
487
|
+
SetException(new OperationCanceledException());
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|