// Tries to enter lock, If retry count over, return false public async Task <bool> TableLockTryEnterAsync(TableSetting tableSetting, string primaryKeyValue, int retryCount = 10) { try { var db = this.redis.GetDatabase(); var key = RedisKey.GetRedisKey_TableLock(tableSetting.tableName, primaryKeyValue); var ts = new TimeSpan(0, 0, 300); int count = 0; while (!await db.StringSetAsync(key, Thread.CurrentThread.ManagedThreadId.ToString(), ts, When.NotExists)) { if (++count >= retryCount) { return(false); } await Task.Delay(1); } return(true); } catch { return(false); } }
public async Task <bool> TableDeleteAsync(string tableName) { TableSetting ts = null; bool enterTableLock = false; try { var db = this.redis.GetDatabase(); ts = await TableGetSettingAsync(tableName); if (null == ts) { return(false); } enterTableLock = await TableLockEnterAsync(ts, ""); var key = RedisKey.GetRedisKey_TablePrimaryKeyList(tableName); // delete every table rows var tasklist = new List <Task <bool> >(); foreach (var primaryKeyValue in db.SetScan(key, "*")) { tasklist.Add(TableDeleteRowAsync(tableName, primaryKeyValue.ToString())); } // delete table schema key = RedisKey.GetRedisKey_TableSchema(tableName); tasklist.Add(db.KeyDeleteAsync(key)); // delete table name id tasklist.Add(db.HashDeleteAsync(Consts.RedisKey_Hash_TableNameIds, tableName)); // delete table auto increment column value tasklist.Add(db.HashDeleteAsync(Consts.RedisKey_Hash_TableAutoIncrementColumnValues, ts.tableID)); foreach (var t in tasklist) { await t; } // remove table setting info this.tableSettingDic.TryRemove(tableName, out ts); return(true); } catch (Exception ex) { return(false); } finally { if (enterTableLock) { await TableLockExit(ts, ""); } } }
public async Task <bool> TableLockExit(TableSetting tableSetting, string primaryKeyValue) { try { var db = this.redis.GetDatabase(); var key = RedisKey.GetRedisKey_TableLock(tableSetting.tableName, primaryKeyValue); await db.KeyDeleteAsync(key); return(true); } catch { return(false); } }
private RedisValue[] GetSelectColumnIndexNumbers(TableSetting ts, List <string> selectColumnNames) { ColumnSetting cs; var len = selectColumnNames.Count; var rva = new RedisValue[len]; for (var i = 0; i < len; i++) { if (ts.tableSchemaDic.TryGetValue(selectColumnNames[i], out cs)) { rva[i] = cs.indexNumber.ToString(); } else { // not existing column throw new Exception(string.Format("Table '{0}' does not have '{1}' column", ts.tableName, selectColumnNames[i])); } } return(rva); }
// wait until enter lock public async Task <bool> TableLockEnterAsync(TableSetting tableSetting, string primaryKeyValue) { try { var db = this.redis.GetDatabase(); var key = RedisKey.GetRedisKey_TableLock(tableSetting.tableName, primaryKeyValue); var ts = new TimeSpan(0, 0, Consts.TableLockExpireSecond); var stw = Stopwatch.StartNew(); bool ret; do { ret = await db.StringSetAsync(key, Thread.CurrentThread.ManagedThreadId.ToString(), ts, When.NotExists); if (false == ret) { await Task.Delay(1); } if (stw.ElapsedMilliseconds > Consts.TableLockWaitLongWarningDurationMiliseconds) { // takes too long time to enter table lock: call event handler to notify this situation to user OnEvent(this, new RedisqlEventArgs(RedisqlEventType.Warn, string.Format("TableLockEnterAsync takes too long time: TableName={0} PrimaryKeyValue={1} {2}ms", tableSetting.tableName, primaryKeyValue, stw.ElapsedMilliseconds))); stw.Restart(); } } while (false == ret); return(true); } catch { return(false); } }
public async Task <bool> TableCreateAsync(string tableName, List <ColumnConfig> columnConfigList, string primaryKeyColumnName) { TableSetting ts = null; bool enterTableLock = false; try { var ccflist = new List <ColumnConfig>(columnConfigList); // check input parameters foreach (var cf in ccflist) { if (cf.name.Equals("_id")) { return(false); // _id column name is reserved. } if (cf.makeRankgeIndex) { switch (cf.type.ToString()) { case "System.Byte": case "System.Int16": case "System.UInt16": case "System.Int32": case "System.UInt32": case "System.Single": case "System.Double": case "System.DateTime": // these types could be range indexed break; default: // other types cannot be range indexed return(false); } } } // every table automatically generate _id column (auto increment) ccflist.Insert(0, new ColumnConfig("_id", typeof(Int64), defaultValue: null)); ts = new TableSetting(); ts.tableName = tableName; enterTableLock = await TableLockEnterAsync(ts, ""); var db = this.redis.GetDatabase(); // check table already exists var ret = await db.HashExistsAsync(Consts.RedisKey_Hash_TableNameIds, tableName); if (ret) { return(false); // already existing table name } // get table id var tableID = await db.StringIncrementAsync(Consts.RedisKey_String_TableNameIds); // write tableName-id await db.HashSetAsync(Consts.RedisKey_Hash_TableNameIds, tableName, tableID); // write table schema var tableSchemaName = RedisKey.GetRedisKey_TableSchema(tableName); int fieldIndex = 0; foreach (var cf in ccflist) { bool pkFlag = false; if (cf.defaultValue == null) { cf.defaultValue = "null"; } if (cf.name.Equals(primaryKeyColumnName)) { pkFlag = true; cf.makeMatchIndex = false; // primary key column does not need additional match index } var value = string.Format("{0},{1},{2},{3},{4},{5}", (fieldIndex++).ToString(), cf.type.ToString(), cf.makeMatchIndex.ToString(), pkFlag.ToString(), cf.makeRankgeIndex.ToString(), cf.defaultValue.ToString()); // fieldIndex, Type, matchIndexFlag, primaryKeyFlag, rangeIndexFlag, defaultValue await db.HashSetAsync(tableSchemaName, cf.name, value); } return(true); } catch (Exception ex) { return(false); } finally { if (enterTableLock) { await TableLockExit(ts, ""); } } }
public async Task <bool> TableCreateNewColumnAsync(string tableName, string columnName, Type columnType, bool makeMatchIndex, bool makeRangeIndex, object defaultValue) { TableSetting ts = null; bool enterTableLock = false; try { if (defaultValue == null) { return(false); } if (!CheckDataType(columnType, defaultValue.ToString())) { return(false); } var db = this.redis.GetDatabase(); ts = await TableGetSettingAsync(tableName); if (null == ts) { return(false); } enterTableLock = await TableLockEnterAsync(ts, ""); var cs = new ColumnSetting(); cs.indexNumber = ts.GetNextColumnIndexNumber(); cs.dataType = columnType; cs.isMatchIndex = makeMatchIndex; cs.isRangeIndex = makeRangeIndex; cs.defaultValue = defaultValue; if (cs.dataType == typeof(DateTime)) { var dvt = defaultValue.ToString().ToLower(); if (dvt.Equals("now")) { defaultValue = DateTime.Now.ToString(); } else if (dvt.Equals("utcnow")) { defaultValue = DateTime.UtcNow.ToString(); } } ts.tableSchemaDic.Add(columnName, cs); if (makeMatchIndex) { ts.matchIndexColumnDic.Add(columnName, cs.indexNumber); } if (makeRangeIndex) { ts.rangeIndexColumnDic.Add(columnName, cs.indexNumber); } ts.columnIndexNameDic.Add(cs.indexNumber.ToString(), columnName); var tableSchemaName = RedisKey.GetRedisKey_TableSchema(tableName); var value = string.Format("{0},{1},{2},{3},{4},{5}", cs.indexNumber.ToString(), cs.dataType.ToString(), makeMatchIndex.ToString(), "False", makeRangeIndex.ToString(), defaultValue.ToString()); // fieldIndex, Type, IndexFlag, primaryKeyFlag, sortFlag await db.HashSetAsync(tableSchemaName, columnName, value); var tasklist = new List <Task <bool> >(); var key = RedisKey.GetRedisKey_TablePrimaryKeyList(tableName); foreach (var primaryKeyValue in db.SetScan(key, "*")) { key = RedisKey.GetRedisKey_TableRow(ts.tableID, primaryKeyValue.ToString()); tasklist.Add(db.HashSetAsync(key, cs.indexNumber.ToString(), defaultValue.ToString())); if (makeMatchIndex) { // make match index key = RedisKey.GetRedisKey_TableMatchIndexColumn(ts.tableID, cs.indexNumber, defaultValue.ToString()); tasklist.Add(db.SetAddAsync(key, primaryKeyValue)); } if (makeRangeIndex) { // make range index key = RedisKey.GetRedisKey_TableRangeIndexColumn(ts.tableID, cs.indexNumber); var score = ConvertToScore(cs.dataType, defaultValue.ToString()); tasklist.Add(db.SortedSetAddAsync(key, primaryKeyValue, score)); } } foreach (var t in tasklist) { if (!await t) { return(false); } } return(true); } catch (Exception ex) { return(false); } finally { if (enterTableLock) { await TableLockExit(ts, ""); } } }
public async Task <bool> TableDeleteExistingColumnAsync(string tableName, string columnName) { TableSetting ts = null; bool enterTableLock = false; try { var db = this.redis.GetDatabase(); ts = await TableGetSettingAsync(tableName); if (null == ts) { return(false); } if (ts.primaryKeyColumnName.Equals(columnName)) { return(false); // Can not delete PrimaryKey column } enterTableLock = await TableLockEnterAsync(ts, ""); ColumnSetting cs; if (!ts.tableSchemaDic.TryGetValue(ts.primaryKeyColumnName, out cs)) { return(false); } var primaryKeyColumnIndex = cs.indexNumber; if (!ts.tableSchemaDic.TryGetValue(columnName, out cs)) { return(false); } var tasklist = new List <Task <RedisValue[]> >(); var key = RedisKey.GetRedisKey_TablePrimaryKeyList(tableName); // read column stored value var rva = new RedisValue[2]; rva[0] = primaryKeyColumnIndex; rva[1] = cs.indexNumber; foreach (var primaryKeyValue in db.SetScan(key, "*")) { key = RedisKey.GetRedisKey_TableRow(ts.tableID, primaryKeyValue.ToString()); tasklist.Add(db.HashGetAsync(key, rva)); } var tasklist2 = new List <Task <bool> >(); foreach (var t in tasklist) { var ret = await t; if (cs.isMatchIndex) { key = RedisKey.GetRedisKey_TableMatchIndexColumn(ts.tableID, cs.indexNumber, ret[1].ToString()); tasklist2.Add(db.SetRemoveAsync(key, ret[0].ToString())); } if (cs.isRangeIndex) { key = RedisKey.GetRedisKey_TableRangeIndexColumn(ts.tableID, cs.indexNumber); tasklist2.Add(db.SortedSetRemoveAsync(key, ret[0].ToString())); } key = RedisKey.GetRedisKey_TableRow(ts.tableID, ret[0].ToString()); tasklist2.Add(db.HashDeleteAsync(key, cs.indexNumber)); } foreach (var t in tasklist2) { if (!await t) { return(false); } } // 데이터를 다 지웠으니 테이블 스키마 제거 ts.tableSchemaDic.Remove(columnName); ts.columnIndexNameDic.Remove(cs.indexNumber.ToString()); Int32 v; if (ts.matchIndexColumnDic.TryGetValue(columnName, out v)) { ts.matchIndexColumnDic.Remove(columnName); } if (ts.rangeIndexColumnDic.TryGetValue(columnName, out v)) { ts.rangeIndexColumnDic.Remove(columnName); } key = RedisKey.GetRedisKey_TableSchema(tableName); await db.HashDeleteAsync(key, columnName); return(true); } catch (Exception ex) { return(false); } finally { if (enterTableLock) { await TableLockExit(ts, ""); } } }
// Remove match index to indexed existing table field public async Task <bool> TableRemoveMatchIndexAsync(string tableName, string columnName) { TableSetting ts = null; bool enterTableLock = false; try { var db = this.redis.GetDatabase(); ts = await TableGetSettingAsync(tableName); if (null == ts) { return(false); } if (ts.primaryKeyColumnName.Equals(columnName)) { return(false); // Can not remove index to primary key column } ColumnSetting cs; if (!ts.tableSchemaDic.TryGetValue(columnName, out cs)) { return(false); } if (!cs.isMatchIndex) { return(false); // Not indexed column. Could not remove index. } enterTableLock = await TableLockEnterAsync(ts, ""); cs.isMatchIndex = false; ts.matchIndexColumnDic.Remove(columnName); var tableSchemaName = RedisKey.GetRedisKey_TableSchema(tableName); var value = string.Format("{0},{1},{2},{3},{4},{5}", cs.indexNumber.ToString(), cs.dataType.ToString(), cs.isMatchIndex.ToString(), "False", cs.isRangeIndex.ToString(), cs.defaultValue.ToString()); // fieldIndex, Type, IndexFlag, primaryKeyFlag, sortFlag await db.HashSetAsync(tableSchemaName, columnName, value); // var tasklist = new List <Task <bool> >(); var key = RedisKey.GetRedisKey_TablePrimaryKeyList(tableName); foreach (var primaryKeyValue in db.SetScan(key, "*")) { key = RedisKey.GetRedisKey_TableRow(ts.tableID, primaryKeyValue.ToString()); var v = await db.HashGetAsync(key, cs.indexNumber); // remove index key = RedisKey.GetRedisKey_TableMatchIndexColumn(ts.tableID, cs.indexNumber, v.ToString()); tasklist.Add(db.SetRemoveAsync(key, primaryKeyValue)); } foreach (var t in tasklist) { if (!await t) { return(false); } } return(true); } catch (Exception ex) { return(false); } finally { if (enterTableLock) { await TableLockExit(ts, ""); } } }
// select one row that matches with primary key column value. // If selectColumnNames is null, select all columns in selected row, or select specified columns only. public async Task <Dictionary <string, string> > TableSelectRowAsync(List <string> selectColumnNames, string tableName, string primaryKeyColumnValue, bool useTableLock = true) { TableSetting ts = null; bool enterTableLock = false; try { var retdic = new Dictionary <string, string>(); ts = await TableGetSettingAsync(tableName); var key = RedisKey.GetRedisKey_TableRow(ts.tableID, primaryKeyColumnValue); var db = this.redis.GetDatabase(); if (useTableLock) { enterTableLock = await TableLockEnterAsync(ts, primaryKeyColumnValue); } if (null == selectColumnNames) { // read all column values var ret = await db.HashGetAllAsync(key); if (null != ret) { var len = ret.Length; for (var i = 0; i < len; i++) { var e = ret[i]; string tableFieldName; if (ts.columnIndexNameDic.TryGetValue(e.Name.ToString(), out tableFieldName)) { retdic.Add(tableFieldName, e.Value.ToString()); } } } } else { // read specified column values var rva = GetSelectColumnIndexNumbers(ts, selectColumnNames); var ret = await db.HashGetAsync(key, rva); if (null != ret) { for (var i = 0; i < rva.Length; i++) { retdic.Add(selectColumnNames[i], ret[i].ToString()); } } } return(retdic); } catch { return(null); } finally { if (enterTableLock) { await TableLockExit(ts, primaryKeyColumnValue); } } }
public async Task <bool> TableDeleteRowAsync(string tableName, string primaryKeyValue, bool useTableLock = true) { TableSetting ts = null; bool enterTableLock = false; try { var db = this.redis.GetDatabase(); List <Task> tasklist = new List <Task>(); ts = await TableGetSettingAsync(tableName); if (null == ts) { return(false); } if (useTableLock) { enterTableLock = await TableLockEnterAsync(ts, primaryKeyValue); } // before delete, read all row to delete index var key = RedisKey.GetRedisKey_TableRow(ts.tableID, primaryKeyValue); var ret = await db.HashGetAllAsync(key); if (null == ret) { return(false); } var fvdic = new Dictionary <string, string>(); foreach (var e in ret) { fvdic.Add(e.Name.ToString(), e.Value.ToString()); } // delete match index foreach (var fieldIndex in ts.matchIndexColumnDic.Values) { key = RedisKey.GetRedisKey_TableMatchIndexColumn(ts.tableID, fieldIndex, fvdic[fieldIndex.ToString()]); tasklist.Add(db.SetRemoveAsync(key, primaryKeyValue)); } // delete range index foreach (var e in ts.rangeIndexColumnDic) { key = RedisKey.GetRedisKey_TableRangeIndexColumn(ts.tableID, e.Value); tasklist.Add(db.SortedSetRemoveAsync(key, primaryKeyValue)); } // delete table row key = RedisKey.GetRedisKey_TableRow(ts.tableID, primaryKeyValue); tasklist.Add(db.KeyDeleteAsync(key)); key = RedisKey.GetRedisKey_TablePrimaryKeyList(tableName); tasklist.Add(db.SetRemoveAsync(key, primaryKeyValue)); foreach (var task in tasklist) { await task; } return(true); } catch (Exception ex) { return(false); } finally { if (enterTableLock) { await TableLockExit(ts, primaryKeyValue); } } }
public async Task <bool> TableUpdateRowAsync(string tableName, Dictionary <string, string> updateColumnNameValuePairs, bool useTableLock = true) { string key; TableSetting ts = null; bool enterTableLock = false; string primaryKeyValue = null; try { var db = this.redis.GetDatabase(); List <Task> tasklist = new List <Task>(); ts = await TableGetSettingAsync(tableName); if (null == ts) { return(false); } if (!updateColumnNameValuePairs.TryGetValue(ts.primaryKeyColumnName, out primaryKeyValue)) { return(false); // primary key column not found } var updatedColumns = new Dictionary <string, Tuple <Int32, string> >(); // Column name, Tuple<columnIndex, updatedValue> foreach (var e in ts.matchIndexColumnDic) { string value; if (updateColumnNameValuePairs.TryGetValue(e.Key, out value)) { updatedColumns.Add(e.Key, new Tuple <Int32, string>(e.Value, value)); // update column is index column } } foreach (var e in ts.rangeIndexColumnDic) { string value; if (updateColumnNameValuePairs.TryGetValue(e.Key, out value)) { if (!updatedColumns.ContainsKey(e.Key)) { updatedColumns.Add(e.Key, new Tuple <Int32, string>(e.Value, value)); // add if not duplicated with index column } } } if (useTableLock) { enterTableLock = await TableLockEnterAsync(ts, primaryKeyValue); } // get values from stored in update columns if (updatedColumns.Count > 0) { int index = 0; var rvArray = new RedisValue[updatedColumns.Count]; foreach (var e in updatedColumns) { rvArray[index++] = e.Value.Item1; } key = RedisKey.GetRedisKey_TableRow(ts.tableID, primaryKeyValue); var ret = await db.HashGetAsync(key, rvArray); index = 0; foreach (var e in updatedColumns) { if (ts.matchIndexColumnDic.ContainsKey(e.Key)) { // update match index column to new value key = RedisKey.GetRedisKey_TableMatchIndexColumn(ts.tableID, e.Value.Item1, ret[index].ToString()); tasklist.Add(db.SetRemoveAsync(key, primaryKeyValue)); key = RedisKey.GetRedisKey_TableMatchIndexColumn(ts.tableID, e.Value.Item1, e.Value.Item2); tasklist.Add(db.SetAddAsync(key, primaryKeyValue)); } if (ts.rangeIndexColumnDic.ContainsKey(e.Key)) { // update range index column to new value key = RedisKey.GetRedisKey_TableRangeIndexColumn(ts.tableID, e.Value.Item1); var score = ConvertToScore(ts.tableSchemaDic[e.Key].dataType, e.Value.Item2); tasklist.Add(db.SortedSetAddAsync(key, primaryKeyValue, score)); } index++; } } int arrayIndex = 0; HashEntry[] heArray = new HashEntry[updateColumnNameValuePairs.Count]; foreach (var e in updateColumnNameValuePairs) { ColumnSetting cs; if (ts.tableSchemaDic.TryGetValue(e.Key, out cs)) { heArray[arrayIndex++] = new HashEntry(cs.indexNumber, e.Value); } } // save table row key = RedisKey.GetRedisKey_TableRow(ts.tableID, primaryKeyValue); tasklist.Add(db.HashSetAsync(key, heArray)); foreach (var task in tasklist) { await task; } return(true); } catch (Exception ex) { return(false); } finally { if (enterTableLock) { await TableLockExit(ts, primaryKeyValue); } } }
public async Task <TableSetting> TableGetSettingAsync(string tableName) { TableSetting ts; var db = this.redis.GetDatabase(); if (!this.tableSettingDic.TryGetValue(tableName, out ts)) { // 아직 로드되지 않았다. redis로부터 읽어들인다. ts = new TableSetting(); ts.tableName = tableName; // get table id var tableID = await db.HashGetAsync(Consts.RedisKey_Hash_TableNameIds, tableName); if (RedisValue.Null == tableID) { return(null); } ts.tableID = Convert.ToInt32(tableID.ToString()); // read table schema var tableSchema = await db.HashGetAllAsync(RedisKey.GetRedisKey_TableSchema(tableName)); if (null == tableSchema) { return(null); } // get table info foreach (var e in tableSchema) { var tokens = e.Value.ToString().Split(','); var cs = new ColumnSetting(); cs.indexNumber = Convert.ToInt32(tokens[0]); switch (tokens[1]) { case "System.Byte": cs.dataType = typeof(Byte); break; case "System.Int16": cs.dataType = typeof(Int16); break; case "System.UInt16": cs.dataType = typeof(UInt16); break; case "System.Int32": cs.dataType = typeof(Int32); break; case "System.UInt32": cs.dataType = typeof(UInt32); break; case "System.Int64": cs.dataType = typeof(Int64); break; case "System.UInt64": cs.dataType = typeof(UInt64); break; case "System.Single": cs.dataType = typeof(Single); break; case "System.Double": cs.dataType = typeof(Double); break; case "System.String": cs.dataType = typeof(String); break; case "System.DateTime": cs.dataType = typeof(DateTime); break; } if (tokens[5].Equals("null")) { cs.defaultValue = null; } else { switch (tokens[1]) { case "System.Byte": cs.defaultValue = Convert.ToByte(tokens[5]); break; case "System.Int16": cs.defaultValue = Convert.ToInt16(tokens[5]); break; case "System.UInt16": cs.defaultValue = Convert.ToUInt16(tokens[5]); break; case "System.Int32": cs.defaultValue = Convert.ToInt32(tokens[5]); break; case "System.UInt32": cs.defaultValue = Convert.ToUInt32(tokens[5]); break; case "System.Int64": cs.defaultValue = Convert.ToInt64(tokens[5]); break; case "System.UInt64": cs.defaultValue = Convert.ToUInt64(tokens[5]); break; case "System.Single": cs.defaultValue = Convert.ToSingle(tokens[5]); break; case "System.Double": cs.defaultValue = Convert.ToDouble(tokens[5]); break; case "System.String": cs.defaultValue = Convert.ToString(tokens[5]); break; case "System.DateTime": if (tokens[5].ToLower().Equals("now")) { cs.defaultValue = "now"; } else if (tokens[5].ToLower().Equals("utcnow")) { cs.defaultValue = "utcnow"; } else { cs.defaultValue = Convert.ToDateTime(tokens[5]); } break; } } cs.isMatchIndex = Convert.ToBoolean(tokens[2]); if (cs.isMatchIndex) { ts.matchIndexColumnDic.Add(e.Name.ToString(), cs.indexNumber); } var fieldPrimaryKeyFlag = Convert.ToBoolean(tokens[3]); if (fieldPrimaryKeyFlag) { ts.primaryKeyColumnName = e.Name; } cs.isRangeIndex = Convert.ToBoolean(tokens[4]); if (cs.isRangeIndex) { ts.rangeIndexColumnDic.Add(e.Name.ToString(), cs.indexNumber); } ts.columnIndexNameDic.Add(cs.indexNumber.ToString(), e.Name.ToString()); ts.tableSchemaDic.Add(e.Name.ToString(), cs); } this.tableSettingDic.TryAdd(tableName, ts); } return(ts); }