opti:deal with escape in the end of field in schemaless
This commit is contained in:
parent
9fd26a5949
commit
d62b82c295
|
@ -80,7 +80,7 @@ extern "C" {
|
||||||
#define IS_SAME_KEY (maxKV->type == kv->type && maxKV->keyLen == kv->keyLen && memcmp(maxKV->key, kv->key, kv->keyLen) == 0)
|
#define IS_SAME_KEY (maxKV->type == kv->type && maxKV->keyLen == kv->keyLen && memcmp(maxKV->key, kv->key, kv->keyLen) == 0)
|
||||||
|
|
||||||
#define IS_SLASH_LETTER_IN_MEASUREMENT(sql) \
|
#define IS_SLASH_LETTER_IN_MEASUREMENT(sql) \
|
||||||
(*((sql)-1) == SLASH && (*(sql) == COMMA || *(sql) == SPACE))
|
(*((sql)-1) == SLASH && (*(sql) == COMMA || *(sql) == SPACE || *(sql) == SLASH))
|
||||||
|
|
||||||
#define MOVE_FORWARD_ONE(sql, len) (memmove((void *)((sql)-1), (sql), len))
|
#define MOVE_FORWARD_ONE(sql, len) (memmove((void *)((sql)-1), (sql), len))
|
||||||
|
|
||||||
|
|
|
@ -20,14 +20,14 @@
|
||||||
|
|
||||||
#include "clientSml.h"
|
#include "clientSml.h"
|
||||||
|
|
||||||
#define IS_COMMA(sql) (*(sql) == COMMA && *((sql)-1) != SLASH)
|
#define IS_COMMA(sql,escapeChar) (*(sql) == COMMA && (*((sql)-1) != SLASH || ((sql)-1 == escapeChar)))
|
||||||
#define IS_SPACE(sql) (*(sql) == SPACE && *((sql)-1) != SLASH)
|
#define IS_SPACE(sql,escapeChar) (*(sql) == SPACE && (*((sql)-1) != SLASH || ((sql)-1 == escapeChar)))
|
||||||
#define IS_EQUAL(sql) (*(sql) == EQUAL && *((sql)-1) != SLASH)
|
#define IS_EQUAL(sql,escapeChar) (*(sql) == EQUAL && (*((sql)-1) != SLASH || ((sql)-1 == escapeChar)))
|
||||||
|
|
||||||
#define IS_SLASH_LETTER_IN_FIELD_VALUE(sql) (*((sql)-1) == SLASH && (*(sql) == QUOTE || *(sql) == SLASH))
|
#define IS_SLASH_LETTER_IN_FIELD_VALUE(sql) (*((sql)-1) == SLASH && (*(sql) == QUOTE || *(sql) == SLASH))
|
||||||
|
|
||||||
#define IS_SLASH_LETTER_IN_TAG_FIELD_KEY(sql) \
|
#define IS_SLASH_LETTER_IN_TAG_FIELD_KEY(sql) \
|
||||||
(*((sql)-1) == SLASH && (*(sql) == COMMA || *(sql) == SPACE || *(sql) == EQUAL))
|
(*((sql)-1) == SLASH && (*(sql) == COMMA || *(sql) == SPACE || *(sql) == EQUAL || *(sql) == SLASH))
|
||||||
|
|
||||||
#define PROCESS_SLASH_IN_FIELD_VALUE(key, keyLen) \
|
#define PROCESS_SLASH_IN_FIELD_VALUE(key, keyLen) \
|
||||||
for (int i = 1; i < keyLen; ++i) { \
|
for (int i = 1; i < keyLen; ++i) { \
|
||||||
|
@ -198,7 +198,7 @@ static int32_t smlProcessTagLine(SSmlHandle *info, char **sql, char *sqlEnd){
|
||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
|
|
||||||
while (*sql < sqlEnd) {
|
while (*sql < sqlEnd) {
|
||||||
if (unlikely(IS_SPACE(*sql))) {
|
if (unlikely(IS_SPACE(*sql,NULL))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,18 +207,21 @@ static int32_t smlProcessTagLine(SSmlHandle *info, char **sql, char *sqlEnd){
|
||||||
size_t keyLen = 0;
|
size_t keyLen = 0;
|
||||||
bool keyEscaped = false;
|
bool keyEscaped = false;
|
||||||
size_t keyLenEscaped = 0;
|
size_t keyLenEscaped = 0;
|
||||||
|
const char *escapeChar = NULL;
|
||||||
|
|
||||||
while (*sql < sqlEnd) {
|
while (*sql < sqlEnd) {
|
||||||
if (unlikely(IS_SPACE(*sql) || IS_COMMA(*sql))) {
|
if (unlikely(IS_SPACE(*sql,escapeChar) || IS_COMMA(*sql,escapeChar))) {
|
||||||
smlBuildInvalidDataMsg(&info->msgBuf, "invalid data", *sql);
|
smlBuildInvalidDataMsg(&info->msgBuf, "invalid data", *sql);
|
||||||
terrno = TSDB_CODE_SML_INVALID_DATA;
|
terrno = TSDB_CODE_SML_INVALID_DATA;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (unlikely(IS_EQUAL(*sql))) {
|
if (unlikely(IS_EQUAL(*sql,escapeChar))) {
|
||||||
keyLen = *sql - key;
|
keyLen = *sql - key;
|
||||||
(*sql)++;
|
(*sql)++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (IS_SLASH_LETTER_IN_TAG_FIELD_KEY(*sql)) {
|
if (IS_SLASH_LETTER_IN_TAG_FIELD_KEY(*sql)) {
|
||||||
|
escapeChar = *sql;
|
||||||
keyLenEscaped++;
|
keyLenEscaped++;
|
||||||
keyEscaped = true;
|
keyEscaped = true;
|
||||||
}
|
}
|
||||||
|
@ -238,15 +241,16 @@ static int32_t smlProcessTagLine(SSmlHandle *info, char **sql, char *sqlEnd){
|
||||||
size_t valueLenEscaped = 0;
|
size_t valueLenEscaped = 0;
|
||||||
while (*sql < sqlEnd) {
|
while (*sql < sqlEnd) {
|
||||||
// parse value
|
// parse value
|
||||||
if (unlikely(IS_SPACE(*sql) || IS_COMMA(*sql))) {
|
if (unlikely(IS_SPACE(*sql,escapeChar) || IS_COMMA(*sql,escapeChar))) {
|
||||||
break;
|
break;
|
||||||
} else if (unlikely(IS_EQUAL(*sql))) {
|
} else if (unlikely(IS_EQUAL(*sql,escapeChar))) {
|
||||||
smlBuildInvalidDataMsg(&info->msgBuf, "invalid data", *sql);
|
smlBuildInvalidDataMsg(&info->msgBuf, "invalid data", *sql);
|
||||||
terrno = TSDB_CODE_SML_INVALID_DATA;
|
terrno = TSDB_CODE_SML_INVALID_DATA;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IS_SLASH_LETTER_IN_TAG_FIELD_KEY(*sql)) {
|
if (IS_SLASH_LETTER_IN_TAG_FIELD_KEY(*sql)) {
|
||||||
|
escapeChar = *sql;
|
||||||
valueLenEscaped++;
|
valueLenEscaped++;
|
||||||
valueEscaped = true;
|
valueEscaped = true;
|
||||||
}
|
}
|
||||||
|
@ -293,7 +297,7 @@ static int32_t smlProcessTagLine(SSmlHandle *info, char **sql, char *sqlEnd){
|
||||||
}
|
}
|
||||||
|
|
||||||
cnt++;
|
cnt++;
|
||||||
if (IS_SPACE(*sql)) {
|
if (IS_SPACE(*sql,escapeChar)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
(*sql)++;
|
(*sql)++;
|
||||||
|
@ -326,7 +330,7 @@ static int32_t smlParseTagLine(SSmlHandle *info, char **sql, char *sqlEnd, SSmlL
|
||||||
static int32_t smlParseColLine(SSmlHandle *info, char **sql, char *sqlEnd, SSmlLineInfo *currElement) {
|
static int32_t smlParseColLine(SSmlHandle *info, char **sql, char *sqlEnd, SSmlLineInfo *currElement) {
|
||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
while (*sql < sqlEnd) {
|
while (*sql < sqlEnd) {
|
||||||
if (unlikely(IS_SPACE(*sql))) {
|
if (unlikely(IS_SPACE(*sql,NULL))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,17 +339,19 @@ static int32_t smlParseColLine(SSmlHandle *info, char **sql, char *sqlEnd, SSmlL
|
||||||
size_t keyLen = 0;
|
size_t keyLen = 0;
|
||||||
bool keyEscaped = false;
|
bool keyEscaped = false;
|
||||||
size_t keyLenEscaped = 0;
|
size_t keyLenEscaped = 0;
|
||||||
|
const char *escapeChar = NULL;
|
||||||
while (*sql < sqlEnd) {
|
while (*sql < sqlEnd) {
|
||||||
if (unlikely(IS_SPACE(*sql) || IS_COMMA(*sql))) {
|
if (unlikely(IS_SPACE(*sql,escapeChar) || IS_COMMA(*sql,escapeChar))) {
|
||||||
smlBuildInvalidDataMsg(&info->msgBuf, "invalid data", *sql);
|
smlBuildInvalidDataMsg(&info->msgBuf, "invalid data", *sql);
|
||||||
return TSDB_CODE_SML_INVALID_DATA;
|
return TSDB_CODE_SML_INVALID_DATA;
|
||||||
}
|
}
|
||||||
if (unlikely(IS_EQUAL(*sql))) {
|
if (unlikely(IS_EQUAL(*sql,escapeChar))) {
|
||||||
keyLen = *sql - key;
|
keyLen = *sql - key;
|
||||||
(*sql)++;
|
(*sql)++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (IS_SLASH_LETTER_IN_TAG_FIELD_KEY(*sql)) {
|
if (IS_SLASH_LETTER_IN_TAG_FIELD_KEY(*sql)) {
|
||||||
|
escapeChar = *sql;
|
||||||
keyLenEscaped++;
|
keyLenEscaped++;
|
||||||
keyEscaped = true;
|
keyEscaped = true;
|
||||||
}
|
}
|
||||||
|
@ -363,7 +369,6 @@ static int32_t smlParseColLine(SSmlHandle *info, char **sql, char *sqlEnd, SSmlL
|
||||||
bool valueEscaped = false;
|
bool valueEscaped = false;
|
||||||
size_t valueLenEscaped = 0;
|
size_t valueLenEscaped = 0;
|
||||||
int quoteNum = 0;
|
int quoteNum = 0;
|
||||||
const char *escapeChar = NULL;
|
|
||||||
while (*sql < sqlEnd) {
|
while (*sql < sqlEnd) {
|
||||||
// parse value
|
// parse value
|
||||||
if (unlikely(*(*sql) == QUOTE && (*(*sql - 1) != SLASH || (*sql - 1) == escapeChar))) {
|
if (unlikely(*(*sql) == QUOTE && (*(*sql - 1) != SLASH || (*sql - 1) == escapeChar))) {
|
||||||
|
@ -374,7 +379,7 @@ static int32_t smlParseColLine(SSmlHandle *info, char **sql, char *sqlEnd, SSmlL
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (quoteNum % 2 == 0 && (unlikely(IS_SPACE(*sql) || IS_COMMA(*sql)))) {
|
if (quoteNum % 2 == 0 && (unlikely(IS_SPACE(*sql,escapeChar) || IS_COMMA(*sql,escapeChar)))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (IS_SLASH_LETTER_IN_FIELD_VALUE(*sql) && (*sql - 1) != escapeChar) {
|
if (IS_SLASH_LETTER_IN_FIELD_VALUE(*sql) && (*sql - 1) != escapeChar) {
|
||||||
|
@ -437,7 +442,7 @@ static int32_t smlParseColLine(SSmlHandle *info, char **sql, char *sqlEnd, SSmlL
|
||||||
}
|
}
|
||||||
|
|
||||||
cnt++;
|
cnt++;
|
||||||
if (IS_SPACE(*sql)) {
|
if (IS_SPACE(*sql,escapeChar)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
(*sql)++;
|
(*sql)++;
|
||||||
|
@ -453,19 +458,18 @@ int32_t smlParseInfluxString(SSmlHandle *info, char *sql, char *sqlEnd, SSmlLine
|
||||||
elements->measure = sql;
|
elements->measure = sql;
|
||||||
// parse measure
|
// parse measure
|
||||||
size_t measureLenEscaped = 0;
|
size_t measureLenEscaped = 0;
|
||||||
|
const char *escapeChar = NULL;
|
||||||
while (sql < sqlEnd) {
|
while (sql < sqlEnd) {
|
||||||
if (unlikely((sql != elements->measure) && IS_SLASH_LETTER_IN_MEASUREMENT(sql))) {
|
if (unlikely(IS_COMMA(sql,escapeChar) || IS_SPACE(sql,escapeChar))) {
|
||||||
elements->measureEscaped = true;
|
|
||||||
measureLenEscaped++;
|
|
||||||
sql++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (unlikely(IS_COMMA(sql))) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unlikely(IS_SPACE(sql))) {
|
if (unlikely((sql != elements->measure) && IS_SLASH_LETTER_IN_MEASUREMENT(sql))) {
|
||||||
break;
|
elements->measureEscaped = true;
|
||||||
|
escapeChar = sql;
|
||||||
|
measureLenEscaped++;
|
||||||
|
sql++;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
sql++;
|
sql++;
|
||||||
}
|
}
|
||||||
|
@ -478,9 +482,12 @@ int32_t smlParseInfluxString(SSmlHandle *info, char *sql, char *sqlEnd, SSmlLine
|
||||||
// to get measureTagsLen before
|
// to get measureTagsLen before
|
||||||
const char *tmp = sql;
|
const char *tmp = sql;
|
||||||
while (tmp < sqlEnd) {
|
while (tmp < sqlEnd) {
|
||||||
if (unlikely(IS_SPACE(tmp))) {
|
if (unlikely(IS_SPACE(tmp,escapeChar))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if(unlikely(IS_SLASH_LETTER_IN_TAG_FIELD_KEY(sql))){
|
||||||
|
escapeChar = sql;
|
||||||
|
}
|
||||||
tmp++;
|
tmp++;
|
||||||
}
|
}
|
||||||
elements->measureTagsLen = tmp - elements->measure;
|
elements->measureTagsLen = tmp - elements->measure;
|
||||||
|
|
|
@ -1018,7 +1018,7 @@ int sml_escape_Test() {
|
||||||
ASSERT(numFields == 5);
|
ASSERT(numFields == 5);
|
||||||
ASSERT(strncmp(fields[1].name, "inode\"i,= s_used", sizeof("inode\"i,= s_used") - 1) == 0);
|
ASSERT(strncmp(fields[1].name, "inode\"i,= s_used", sizeof("inode\"i,= s_used") - 1) == 0);
|
||||||
ASSERT(strncmp(fields[2].name, "total", sizeof("total") - 1) == 0);
|
ASSERT(strncmp(fields[2].name, "total", sizeof("total") - 1) == 0);
|
||||||
ASSERT(strncmp(fields[3].name, "inode\"i,= s_f\\\\ree", sizeof("inode\"i,= s_f\\\\ree") - 1) == 0);
|
ASSERT(strncmp(fields[3].name, "inode\"i,= s_f\\ree", sizeof("inode\"i,= s_f\\ree") - 1) == 0);
|
||||||
ASSERT(strncmp(fields[4].name, "dev\"i,= ce", sizeof("dev\"i,= ce") - 1) == 0);
|
ASSERT(strncmp(fields[4].name, "dev\"i,= ce", sizeof("dev\"i,= ce") - 1) == 0);
|
||||||
|
|
||||||
TAOS_ROW row = NULL;
|
TAOS_ROW row = NULL;
|
||||||
|
@ -1044,6 +1044,91 @@ int sml_escape_Test() {
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// test field with end of escape
|
||||||
|
int sml_escape1_Test() {
|
||||||
|
TAOS *taos = taos_connect("localhost", "root", "taosdata", NULL, 0);
|
||||||
|
|
||||||
|
TAOS_RES *pRes = taos_query(taos, "create database if not exists db_escape");
|
||||||
|
taos_free_result(pRes);
|
||||||
|
|
||||||
|
pRes = taos_query(taos, "use db_escape");
|
||||||
|
taos_free_result(pRes);
|
||||||
|
|
||||||
|
const char *sql[] = {
|
||||||
|
"stab,t1\\=1 c1=3,c2=\"32fw\" 1661943970000000000",
|
||||||
|
"stab,t1=1\\ c1=3,c2=\"32fw\" 1661943980000000000",
|
||||||
|
"stab,t1=1 c1\\=3,c2=\"32fw\" 1661943990000000000",
|
||||||
|
};
|
||||||
|
for(int i = 0; i < sizeof(sql) / sizeof(sql[0]); i++){
|
||||||
|
pRes = taos_schemaless_insert(taos, (char**)&sql[i], 1, TSDB_SML_LINE_PROTOCOL, 0);
|
||||||
|
int code = taos_errno(pRes);
|
||||||
|
ASSERT(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *sql1[] = {
|
||||||
|
"stab\\,t1=1 c1=3,c2=\"32fw\" 1661943960000000000",
|
||||||
|
"stab\\\\,t1=1 c1=3,c2=\"32fw\" 1661943960000000000",
|
||||||
|
"stab,t1\\\\=1 c1=3,c2=\"32fw\" 1661943970000000000",
|
||||||
|
"stab,t1=1\\\\ c1=3,c2=\"32fw\" 1661943980000000000",
|
||||||
|
"stab,t1=1 c1\\\\=3,c2=\"32fw\" 1661943990000000000",
|
||||||
|
};
|
||||||
|
pRes = taos_schemaless_insert(taos, (char **)sql1, sizeof(sql1) / sizeof(sql1[0]), TSDB_SML_LINE_PROTOCOL, 0);
|
||||||
|
printf("%s result:%s, rows:%d\n", __FUNCTION__, taos_errstr(pRes), taos_affected_rows(pRes));
|
||||||
|
int code = taos_errno(pRes);
|
||||||
|
ASSERT(!code);
|
||||||
|
ASSERT(taos_affected_rows(pRes) == 5);
|
||||||
|
taos_free_result(pRes);
|
||||||
|
|
||||||
|
pRes = taos_query(taos, "select * from stab order by _ts"); //check stable name
|
||||||
|
ASSERT(pRes);
|
||||||
|
int fieldNum = taos_field_count(pRes);
|
||||||
|
ASSERT(fieldNum == 6);
|
||||||
|
printf("fieldNum:%d\n", fieldNum);
|
||||||
|
|
||||||
|
int numFields = taos_num_fields(pRes);
|
||||||
|
TAOS_FIELD *fields = taos_fetch_fields(pRes);
|
||||||
|
ASSERT(numFields == 6);
|
||||||
|
ASSERT(strncmp(fields[1].name, "c1", sizeof("c1") - 1) == 0);
|
||||||
|
ASSERT(strncmp(fields[2].name, "c2", sizeof("c2") - 1) == 0);
|
||||||
|
ASSERT(strncmp(fields[3].name, "c1\\", sizeof("c1\\") - 1) == 0);
|
||||||
|
ASSERT(strncmp(fields[4].name, "t1\\", sizeof("t1\\") - 1) == 0);
|
||||||
|
ASSERT(strncmp(fields[5].name, "t1", sizeof("t1") - 1) == 0);
|
||||||
|
|
||||||
|
TAOS_ROW row = NULL;
|
||||||
|
int32_t rowIndex = 0;
|
||||||
|
while ((row = taos_fetch_row(pRes)) != NULL) {
|
||||||
|
int64_t ts = *(int64_t *)row[0];
|
||||||
|
|
||||||
|
if (rowIndex == 0) {
|
||||||
|
ASSERT(ts == 1661943970000);
|
||||||
|
ASSERT(*(double *)row[1] == 3);
|
||||||
|
ASSERT(strncmp(row[2], "32fw", sizeof("32fw") - 1) == 0);
|
||||||
|
ASSERT(row[3] == NULL);
|
||||||
|
ASSERT(strncmp(row[4], "1", sizeof("1") - 1) == 0);
|
||||||
|
ASSERT(row[5] == NULL);
|
||||||
|
}else if (rowIndex == 1) {
|
||||||
|
ASSERT(ts == 1661943980000);
|
||||||
|
ASSERT(*(double *)row[1] == 3);
|
||||||
|
ASSERT(strncmp(row[2], "32fw", sizeof("32fw") - 1) == 0);
|
||||||
|
ASSERT(row[3] == NULL);
|
||||||
|
ASSERT(row[4] == NULL);
|
||||||
|
ASSERT(strncmp(row[5], "1\\", sizeof("1\\") - 1) == 0);
|
||||||
|
}else if (rowIndex == 2) {
|
||||||
|
ASSERT(ts == 1661943990000);
|
||||||
|
ASSERT(row[1] == NULL);
|
||||||
|
ASSERT(strncmp(row[2], "32fw", sizeof("32fw") - 1) == 0);
|
||||||
|
ASSERT(*(double *)row[3] == 3);
|
||||||
|
ASSERT(row[4] == NULL);
|
||||||
|
ASSERT(strncmp(row[5], "1", sizeof("1") - 1) == 0);
|
||||||
|
}
|
||||||
|
rowIndex++;
|
||||||
|
}
|
||||||
|
taos_free_result(pRes);
|
||||||
|
taos_close(taos);
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
int sml_19221_Test() {
|
int sml_19221_Test() {
|
||||||
TAOS *taos = taos_connect("localhost", "root", "taosdata", NULL, 0);
|
TAOS *taos = taos_connect("localhost", "root", "taosdata", NULL, 0);
|
||||||
|
|
||||||
|
@ -1775,17 +1860,14 @@ int main(int argc, char *argv[]) {
|
||||||
ASSERT(ret);
|
ASSERT(ret);
|
||||||
ret = sml_escape_Test();
|
ret = sml_escape_Test();
|
||||||
ASSERT(!ret);
|
ASSERT(!ret);
|
||||||
|
ret = sml_escape1_Test();
|
||||||
|
ASSERT(!ret);
|
||||||
ret = sml_ts3116_Test();
|
ret = sml_ts3116_Test();
|
||||||
ASSERT(!ret);
|
ASSERT(!ret);
|
||||||
ret = sml_ts2385_Test(); // this test case need config sml table name using ./sml_test config_file
|
ret = sml_ts2385_Test(); // this test case need config sml table name using ./sml_test config_file
|
||||||
ASSERT(!ret);
|
ASSERT(!ret);
|
||||||
ret = sml_ts3303_Test();
|
ret = sml_ts3303_Test();
|
||||||
ASSERT(!ret);
|
ASSERT(!ret);
|
||||||
|
|
||||||
// for(int i = 0; i < sizeof(str)/sizeof(str[0]); i++){
|
|
||||||
// printf("str:%s \t %d\n", str[i], smlCalTypeSum(str[i], strlen(str[i])));
|
|
||||||
// }
|
|
||||||
// int ret = 0;
|
|
||||||
ret = sml_ttl_Test();
|
ret = sml_ttl_Test();
|
||||||
ASSERT(!ret);
|
ASSERT(!ret);
|
||||||
ret = sml_ts2164_Test();
|
ret = sml_ts2164_Test();
|
||||||
|
|
Loading…
Reference in New Issue