diff --git a/opengemini/client.go b/opengemini/client.go index bd23e81..e93865e 100644 --- a/opengemini/client.go +++ b/opengemini/client.go @@ -21,7 +21,7 @@ type Client interface { Query(query Query) (*QueryResult, error) // WritePoint write single point to assigned database. If you don't want to implement callbackFunc to receive error - // in writing, you cloud use opengemini.CallbackDummy. + // in writing, you could use opengemini.CallbackDummy. WritePoint(database string, point *Point, callbackFunc WriteCallback) error // WritePointWithRp write single point with retention policy. If you don't want to implement callbackFunc to // receive error in writing, you cloud use opengemini.CallbackDummy. @@ -46,8 +46,30 @@ type Client interface { ShowRetentionPolicies(database string) ([]RetentionPolicy, error) DropRetentionPolicy(database, retentionPolicy string) error - ShowMeasurements(database, retentionPolicy string) ([]string, error) - DropMeasurement(database, retentionPolicy, measurement string) error + // ShowMeasurements use command `SHOW MEASUREMENT` to view the measurements created in the database, calling + // NewMeasurementBuilder.Database("db0").RetentionPolicy("rp0").Show() is the best way to set up + // the builder, don't forget to set the database otherwise it will return an error + ShowMeasurements(builder ShowMeasurementBuilder) ([]string, error) + // DropMeasurement use command `DROP MEASUREMENT` to delete measurement, deleting a measurement + // will delete all indexes, series and data. if retentionPolicy is empty, use default retention policy, calling + // NewMeasurementBuilder.Database("db0").RetentionPolicy("rp0").Drop() is the best way to set up the builder, don't + // forget to set the database otherwise it will return an error + DropMeasurement(builder DropMeasurementBuilder) error + // CreateMeasurement use command `CREATE MEASUREMENT` to create measurement, openGemini supports + // automatic table creation when writing data, but in the following three situations, tables need + // to be created in advance. + // - specify a tag as partition key + // - text search + // - high series cardinality storage engine(HSCE) + // calling NewMeasurementBuilder().Database(databaseName).Measurement(measurement). + // Create().TagList([]string{"tag1", "tag2"}).FieldMap(map[string]fieldType{ + // "f_int64": FieldTypeInt64, + // "f_float": FieldTypeFloat64, + // "f_bool": FieldTypeBool, + // "f_string": FieldTypeString, + // }).ShardKeys([]string{"tag1"}) is the best way to set up the + // builder, don't forget to set the database otherwise it will return an error + CreateMeasurement(builder CreateMeasurementBuilder) error ShowTagKeys(database, command string) ([]ValuesResult, error) ShowTagValues(database, command string) ([]ValuesResult, error) diff --git a/opengemini/error.go b/opengemini/error.go index cc89182..ce61da4 100644 --- a/opengemini/error.go +++ b/opengemini/error.go @@ -7,6 +7,7 @@ var ( ErrRetentionPolicy = errors.New("empty retention policy") ErrEmptyMeasurement = errors.New("empty measurement") ErrEmptyCommand = errors.New("empty command") + ErrEmptyTagOrField = errors.New("empty tag or field") ) // checkDatabaseName checks if the database name is empty and returns an error if it is. @@ -17,6 +18,14 @@ func checkDatabaseName(database string) error { return nil } +// checkMeasurementName checks if the measurement name is empty and returns an error if it is. +func checkMeasurementName(mst string) error { + if len(mst) == 0 { + return ErrEmptyMeasurement + } + return nil +} + func checkDatabaseAndPolicy(database, retentionPolicy string) error { if len(database) == 0 { return ErrEmptyDatabaseName diff --git a/opengemini/measurement.go b/opengemini/measurement.go index 02e3b79..26e0de0 100644 --- a/opengemini/measurement.go +++ b/opengemini/measurement.go @@ -13,13 +13,23 @@ type ValuesResult struct { Values []interface{} } -func (c *client) ShowMeasurements(database, retentionPolicy string) ([]string, error) { - err := checkDatabaseName(database) +func (c *client) ShowMeasurements(builder ShowMeasurementBuilder) ([]string, error) { + base := builder.getMeasurementBase() + err := checkDatabaseName(base.database) if err != nil { return nil, err } - queryResult, err := c.queryPost(Query{Database: database, RetentionPolicy: retentionPolicy, Command: "SHOW MEASUREMENTS"}) + command, err := builder.build() + if err != nil { + return nil, err + } + + queryResult, err := c.queryPost(Query{ + Database: base.database, + RetentionPolicy: base.retentionPolicy, + Command: command, + }) if err != nil { return nil, err @@ -33,17 +43,55 @@ func (c *client) ShowMeasurements(database, retentionPolicy string) ([]string, e return queryResult.convertMeasurements(), nil } -func (c *client) DropMeasurement(database, retentionPolicy, measurement string) error { - err := checkDatabaseAndMeasurement(database, measurement) +func (c *client) DropMeasurement(builder DropMeasurementBuilder) error { + base := builder.getMeasurementBase() + err := checkDatabaseName(base.database) + if err != nil { + return err + } + + command, err := builder.build() + if err != nil { + return err + } + req := requestDetails{ + queryValues: make(url.Values), + } + req.queryValues.Add("db", base.database) + req.queryValues.Add("rp", base.retentionPolicy) + req.queryValues.Add("q", command) + resp, err := c.executeHttpPost("/query", req) + if err != nil { + return err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return errors.New("read resp failed, error: " + err.Error()) + } + if resp.StatusCode != http.StatusOK { + return errors.New("error resp, code: " + resp.Status + "body: " + string(body)) + } + return nil +} + +func (c *client) CreateMeasurement(builder CreateMeasurementBuilder) error { + base := builder.getMeasurementBase() + err := checkDatabaseName(base.database) + if err != nil { + return err + } + + command, err := builder.build() if err != nil { return err } req := requestDetails{ queryValues: make(url.Values), } - req.queryValues.Add("db", database) - req.queryValues.Add("rp", retentionPolicy) - req.queryValues.Add("q", "DROP MEASUREMENT \""+measurement+"\"") + req.queryValues.Add("db", base.database) + req.queryValues.Add("rp", base.retentionPolicy) + req.queryValues.Add("q", command) resp, err := c.executeHttpPost("/query", req) if err != nil { return err diff --git a/opengemini/measurement_builder.go b/opengemini/measurement_builder.go new file mode 100644 index 0000000..66b3b35 --- /dev/null +++ b/opengemini/measurement_builder.go @@ -0,0 +1,305 @@ +package opengemini + +import ( + "errors" + "fmt" + "strings" +) + +type shardType string + +const ( + ShardTypeHash shardType = "HASH" + ShardTypeRange shardType = "RANGE" +) + +type fieldType string + +const ( + FieldTypeInt64 fieldType = "INT64" + FieldTypeFloat64 fieldType = "FLOAT64" + FieldTypeString fieldType = "STRING" + FieldTypeBool fieldType = "BOOL" +) + +type engineType string + +const ( + EngineTypeColumnStore engineType = "columnstore" +) + +type measurementCommand string + +const ( + MeasureCreate measurementCommand = "CREATE" + MeasureDrop measurementCommand = "DROP" + MeasureShow measurementCommand = "SHOW" +) + +type measurementBase struct { + database string + retentionPolicy string + measurement string +} + +type measurementBuilder struct { + // command specify the command type + command measurementCommand + // measurementBase is the base information of measurement + measurementBase + // filter use regexp to filter measurements + filter *ComparisonCondition + // tagList is the tags schema for measurement + tagList []string + // fields is the fields schema for measurement + fields []string + fullText bool + shardType shardType + shardKeys []string + indexType string + indexList []string + engineType engineType + primaryKey []string + sortKeys []string +} + +func (m *measurementBuilder) TagList(tagList []string) CreateMeasurementBuilder { + for _, tag := range tagList { + m.tagList = append(m.tagList, tag+" TAG") + } + return m +} + +func (m *measurementBuilder) FieldMap(fields map[string]fieldType) CreateMeasurementBuilder { + for key, value := range fields { + m.fields = append(m.fields, key+" "+string(value)+" FIELD") + } + return m +} + +func (m *measurementBuilder) ShardKeys(shardKeys []string) CreateMeasurementBuilder { + m.shardKeys = shardKeys + return m +} + +func (m *measurementBuilder) ShardType(shardType shardType) CreateMeasurementBuilder { + m.shardType = shardType + return m +} + +func (m *measurementBuilder) FullTextIndex() CreateMeasurementBuilder { + m.indexType = "text" + return m +} + +func (m *measurementBuilder) IndexList(indexList []string) CreateMeasurementBuilder { + m.indexList = indexList + return m +} + +func (m *measurementBuilder) EngineType(engineType engineType) CreateMeasurementBuilder { + m.engineType = engineType + return m +} + +func (m *measurementBuilder) PrimaryKey(primaryKey []string) CreateMeasurementBuilder { + m.primaryKey = primaryKey + return m +} + +func (m *measurementBuilder) SortKeys(sortKeys []string) CreateMeasurementBuilder { + m.sortKeys = sortKeys + return m +} + +func (m *measurementBuilder) Filter(operator ComparisonOperator, regex string) ShowMeasurementBuilder { + m.filter = NewComparisonCondition("MEASUREMENT", operator, regex) + return m +} + +func (m *measurementBuilder) build() (string, error) { + err := checkDatabaseName(m.database) + if err != nil { + return "", err + } + switch m.command { + case MeasureCreate: + if len(m.tagList) == 0 && len(m.fields) == 0 { + return "", ErrEmptyTagOrField + } + var buffer strings.Builder + buffer.WriteString(`CREATE MEASUREMENT ` + m.measurement + " (") + if len(m.tagList) != 0 { + buffer.WriteString(strings.Join(m.tagList, ",")) + } + if len(m.tagList) != 0 && len(m.fields) != 0 { + buffer.WriteString(",") + } + if len(m.fields) != 0 { + buffer.WriteString(strings.Join(m.fields, ",")) + } + buffer.WriteString(")") + var withIdentifier bool + if m.indexType != "" && len(m.indexList) == 0 { + return "", errors.New("empty index list") + } + if m.indexType != "" { + withIdentifier = true + buffer.WriteString(" WITH ") + buffer.WriteString(" INDEXTYPE " + m.indexType) + buffer.WriteString(" INDEXLIST " + strings.Join(m.indexList, ",")) + } + if m.engineType != "" { + if !withIdentifier { + withIdentifier = true + buffer.WriteString(" WITH ") + } + buffer.WriteString(" ENGINETYPE = " + string(m.engineType)) + } + if len(m.shardKeys) != 0 { + if !withIdentifier { + withIdentifier = true + buffer.WriteString(" WITH ") + } + buffer.WriteString(" SHARDKEY " + strings.Join(m.shardKeys, ",")) + } + if m.shardType != "" { + if !withIdentifier { + withIdentifier = true + buffer.WriteString(" WITH ") + } + buffer.WriteString(" TYPE " + string(m.shardType)) + } + if len(m.primaryKey) != 0 { + if !withIdentifier { + withIdentifier = true + buffer.WriteString(" WITH ") + } + buffer.WriteString(" PRIMARYKEY " + strings.Join(m.primaryKey, ",")) + } + if len(m.sortKeys) != 0 { + if !withIdentifier { + withIdentifier = true + buffer.WriteString(" WITH ") + } + buffer.WriteString(" SORTKEY " + strings.Join(m.sortKeys, ",")) + } + return buffer.String(), nil + case MeasureDrop: + err := checkMeasurementName(m.measurement) + if err != nil { + return "", err + } + if m.retentionPolicy == "" { + m.retentionPolicy = "autogen" + } + return fmt.Sprintf(`DROP MEASUREMENT "%s"."%s"`, m.retentionPolicy, m.measurement), nil + case MeasureShow: + var buf strings.Builder + buf.WriteString(`SHOW MEASUREMENTS`) + if m.filter != nil { + buf.WriteString(" WITH " + m.filter.string()) + } + return buf.String(), nil + default: + return "", fmt.Errorf("invalid command: %s", m.command) + } +} + +func (m *measurementBuilder) Show() ShowMeasurementBuilder { + m.command = MeasureShow + return m +} + +func (m *measurementBuilder) Drop() DropMeasurementBuilder { + m.command = MeasureDrop + return m +} + +func (m *measurementBuilder) Create() CreateMeasurementBuilder { + m.command = MeasureCreate + return m +} + +// Database specify measurement in database +func (m *measurementBuilder) Database(database string) MeasurementBuilder { + m.database = database + return m +} + +// Measurement specify measurement name +func (m *measurementBuilder) Measurement(measurement string) MeasurementBuilder { + m.measurement = measurement + return m +} + +// RetentionPolicy specify retention policy +func (m *measurementBuilder) RetentionPolicy(rp string) MeasurementBuilder { + m.retentionPolicy = rp + return m +} + +// getMeasurementBase get measurement info base +func (m *measurementBuilder) getMeasurementBase() measurementBase { + return m.measurementBase +} + +type MeasurementBuilder interface { + // Database specify measurement in database + Database(database string) MeasurementBuilder + // Measurement specify measurement name + Measurement(measurement string) MeasurementBuilder + // RetentionPolicy specify retention policy + RetentionPolicy(rp string) MeasurementBuilder + // Show use command `SHOW MEASUREMENT` to show measurements + Show() ShowMeasurementBuilder + // Drop use command `DROP MEASUREMENT` to drop measurement + Drop() DropMeasurementBuilder + // Create use command `CREATE MEASUREMENT` to create measurement + Create() CreateMeasurementBuilder +} + +// DropMeasurementBuilder drop measurement, if measurement not exist, return error +type DropMeasurementBuilder interface { + build() (string, error) + getMeasurementBase() measurementBase +} + +type ShowMeasurementBuilder interface { + // Filter use regular statements to filter measurements, operator support Match, NotMatch, Equals, NotEquals + Filter(operator ComparisonOperator, regex string) ShowMeasurementBuilder + build() (string, error) + getMeasurementBase() measurementBase +} + +type CreateMeasurementBuilder interface { + // TagList specify tag list to create measurement + TagList(tagList []string) CreateMeasurementBuilder + // FieldMap specify field map to create measurement + FieldMap(fields map[string]fieldType) CreateMeasurementBuilder + // ShardKeys specify shard keys(tag as partition key) to create measurement, required when use + // high series cardinality storage engine(HSCE) + ShardKeys(shardKeys []string) CreateMeasurementBuilder + // ShardType specify shard type to create measurement, support ShardTypeHash and ShardTypeRange two ways to + // break up data, required when use high series cardinality storage engine(HSCE) + ShardType(shardType shardType) CreateMeasurementBuilder + // FullTextIndex required when want measurement support full-text index + FullTextIndex() CreateMeasurementBuilder + // IndexList required when specify which Field fields to create a full-text index on, + // these fields must be 'string' data type + IndexList(indexList []string) CreateMeasurementBuilder + // EngineType required when want measurement support HSCE, set EngineTypeColumnStore + EngineType(engineType engineType) CreateMeasurementBuilder + // PrimaryKey required when use HSCE, such as the primary key is `location` and `direction`, which means that the + // storage engine will create indexes on these two fields + PrimaryKey(primaryKey []string) CreateMeasurementBuilder + // SortKeys required when use HSCE, specify the data sorting method inside the storage engine, time means sorting + // by time, and can also be changed to rtt or direction, or even other fields in the table + SortKeys(sortKeys []string) CreateMeasurementBuilder + build() (string, error) + getMeasurementBase() measurementBase +} + +func NewMeasurementBuilder() MeasurementBuilder { + return &measurementBuilder{} +} diff --git a/opengemini/measurement_test.go b/opengemini/measurement_test.go index 8f0942e..f4c6ae2 100644 --- a/opengemini/measurement_test.go +++ b/opengemini/measurement_test.go @@ -7,6 +7,31 @@ import ( "time" ) +func TestClient_ShowMeasurements(t *testing.T) { + c := testDefaultClient(t) + databaseName := randomDatabaseName() + measurement := "prefix_" + randomMeasurement() + err := c.CreateDatabase(databaseName) + require.Nil(t, err) + err = c.CreateMeasurement(NewMeasurementBuilder().Database(databaseName).Measurement(measurement). + Create().TagList([]string{"tag1", "tag2"}).FieldMap(map[string]fieldType{ + "f_int64": FieldTypeInt64, + "f_float": FieldTypeFloat64, + "f_bool": FieldTypeBool, + "f_string": FieldTypeString, + })) + require.Nil(t, err) + time.Sleep(time.Second * 5) + measurements, err := c.ShowMeasurements(NewMeasurementBuilder().Database(databaseName). + Show().Filter(Match, "/prefix.*/")) + require.Nil(t, err) + require.Contains(t, measurements, measurement) + measurements, err = c.ShowMeasurements(NewMeasurementBuilder().Database(databaseName). + Show().Filter(Match, "/suffix.*/")) + require.Nil(t, err) + require.Equal(t, 0, len(measurements)) +} + func TestClientDropMeasurementExistSpecifyRp(t *testing.T) { c := testDefaultClient(t) databaseName := randomDatabaseName() @@ -23,18 +48,21 @@ func TestClientDropMeasurementExistSpecifyRp(t *testing.T) { Time: time.Time{}, Tags: nil, Fields: map[string]interface{}{ - "value": 1, + "string": 1, }, }, }) require.Nil(t, err) time.Sleep(time.Second * 5) - measurements, err := c.ShowMeasurements(databaseName, retentionPolicy) + measurements, err := c.ShowMeasurements(NewMeasurementBuilder().Database(databaseName). + RetentionPolicy(retentionPolicy).Show()) require.Nil(t, err) require.Contains(t, measurements, measurement) - err = c.DropMeasurement(databaseName, retentionPolicy, measurement) + err = c.DropMeasurement(NewMeasurementBuilder().Database(databaseName).Measurement(measurement). + RetentionPolicy(retentionPolicy).Drop()) require.Nil(t, err) - measurements, err = c.ShowMeasurements(databaseName, retentionPolicy) + measurements, err = c.ShowMeasurements(NewMeasurementBuilder().Database(databaseName). + RetentionPolicy(retentionPolicy).Show()) require.Nil(t, err) require.NotContains(t, measurements, measurement) err = c.DropRetentionPolicy(databaseName, retentionPolicy) @@ -49,7 +77,8 @@ func TestClientDropMeasurementNonExistent(t *testing.T) { require.Nil(t, err) err = c.CreateRetentionPolicy(databaseName, RpConfig{Name: retentionPolicy, Duration: "3d"}, false) require.Nil(t, err) - err = c.DropMeasurement(databaseName, retentionPolicy, "non_existent_measurement") + err = c.DropMeasurement(NewMeasurementBuilder().Database(databaseName).Measurement("non_existent_measurement"). + RetentionPolicy(retentionPolicy).Drop()) require.Nil(t, err) err = c.DropRetentionPolicy(databaseName, retentionPolicy) require.Nil(t, err) @@ -65,7 +94,7 @@ func TestClientDropMeasurementEmptyMeasurementName(t *testing.T) { require.Nil(t, err) err = c.CreateRetentionPolicy(databaseName, RpConfig{Name: retentionPolicy, Duration: "3d"}, false) require.Nil(t, err) - err = c.DropMeasurement(databaseName, retentionPolicy, "") + err = c.DropMeasurement(NewMeasurementBuilder().Database(databaseName).RetentionPolicy(retentionPolicy).Drop()) require.NotNil(t, err) err = c.DropRetentionPolicy(databaseName, retentionPolicy) require.Nil(t, err) @@ -86,18 +115,18 @@ func TestClientDropMeasurementEmptyRetentionPolicy(t *testing.T) { Time: time.Time{}, Tags: nil, Fields: map[string]interface{}{ - "value": 1, + "string": 1, }, }, }) require.Nil(t, err) time.Sleep(time.Second * 5) - measurements, err := c.ShowMeasurements(databaseName, "") + measurements, err := c.ShowMeasurements(NewMeasurementBuilder().Database(databaseName).Show()) require.Nil(t, err) require.Contains(t, measurements, measurement) - err = c.DropMeasurement(databaseName, "", measurement) + err = c.DropMeasurement(NewMeasurementBuilder().Database(databaseName).Measurement(measurement).Drop()) require.Nil(t, err) - measurements, err = c.ShowMeasurements(databaseName, "") + measurements, err = c.ShowMeasurements(NewMeasurementBuilder().Database(databaseName).Show()) require.Nil(t, err) require.NotContains(t, measurements, measurement) err = c.DropDatabase(databaseName) @@ -108,6 +137,74 @@ func TestClientDropMeasurementEmptyDatabaseName(t *testing.T) { c := testDefaultClient(t) retentionPolicy := randomRetentionPolicy() measurement := randomMeasurement() - err := c.DropMeasurement("", retentionPolicy, measurement) + err := c.DropMeasurement(NewMeasurementBuilder().Database("").Measurement(measurement). + RetentionPolicy(retentionPolicy).Drop()) require.NotNil(t, err) } + +func TestClient_CreateMeasurement(t *testing.T) { + c := testDefaultClient(t) + databaseName := randomDatabaseName() + measurement := randomMeasurement() + err := c.CreateDatabase(databaseName) + require.Nil(t, err) + err = c.CreateMeasurement(NewMeasurementBuilder().Database(databaseName).Measurement(measurement). + Create().TagList([]string{"tag1", "tag2"}).FieldMap(map[string]fieldType{ + "f_int64": FieldTypeInt64, + "f_float": FieldTypeFloat64, + "f_bool": FieldTypeBool, + "f_string": FieldTypeString, + }).ShardKeys([]string{"tag1"})) + require.Nil(t, err) + time.Sleep(time.Second * 5) + measurements, err := c.ShowMeasurements(NewMeasurementBuilder().Database(databaseName).Show()) + require.Nil(t, err) + require.Contains(t, measurements, measurement) + err = c.DropDatabase(databaseName) + require.Nil(t, err) +} + +func TestClient_CreateMeasurementWithHSCE(t *testing.T) { + c := testDefaultClient(t) + databaseName := randomDatabaseName() + measurement := randomMeasurement() + err := c.CreateDatabase(databaseName) + require.Nil(t, err) + err = c.CreateMeasurement(NewMeasurementBuilder().Database(databaseName).Measurement(measurement). + Create().TagList([]string{"tag1", "tag2", "tag3", "tag4"}).FieldMap(map[string]fieldType{ + "f_int64": FieldTypeInt64, + "f_float": FieldTypeFloat64, + "f_bool": FieldTypeBool, + "f_string": FieldTypeString, + }).ShardKeys([]string{"tag1"}).EngineType(EngineTypeColumnStore).IndexList([]string{"f_int64", "f_string"}). + PrimaryKey([]string{"f_string"}).SortKeys([]string{"f_string"})) + require.Nil(t, err) + time.Sleep(time.Second * 5) + measurements, err := c.ShowMeasurements(NewMeasurementBuilder().Database(databaseName).Show()) + require.Nil(t, err) + require.Contains(t, measurements, measurement) + err = c.DropDatabase(databaseName) + require.Nil(t, err) +} + +func TestClient_CreateMeasurementWithFullTextIndex(t *testing.T) { + c := testDefaultClient(t) + databaseName := randomDatabaseName() + measurement := randomMeasurement() + err := c.CreateDatabase(databaseName) + require.Nil(t, err) + err = c.CreateMeasurement(NewMeasurementBuilder().Database(databaseName).Measurement(measurement). + Create().TagList([]string{"tag1", "tag2", "tag3", "tag4"}).FieldMap(map[string]fieldType{ + "f_int64": FieldTypeInt64, + "f_float": FieldTypeFloat64, + "f_bool": FieldTypeBool, + "f_string": FieldTypeString, + }).ShardKeys([]string{"tag1"}).FullTextIndex().IndexList([]string{"f_int64", "f_string"})) + require.Nil(t, err) + time.Sleep(time.Second * 5) + measurements, err := c.ShowMeasurements(NewMeasurementBuilder().Database(databaseName).Show()) + require.Nil(t, err) + require.Contains(t, measurements, measurement) + err = c.DropDatabase(databaseName) + require.Nil(t, err) +} diff --git a/opengemini/point.go b/opengemini/point.go index 7589f18..1f75dd4 100644 --- a/opengemini/point.go +++ b/opengemini/point.go @@ -57,7 +57,7 @@ func (p Precision) Epoch() string { type Point struct { Measurement string - // Precision Timestamp precision ,default value is PrecisionNanosecond + // Precision Timestamp precision ,default string is PrecisionNanosecond Precision Precision Time time.Time Tags map[string]string @@ -179,7 +179,7 @@ func (enc *LineProtocolEncoder) writeFieldValue(v interface{}) error { err = enc.writeByte('F') } default: - err = errors.New("unsupported field value type") + err = errors.New("unsupported field string type") } return err diff --git a/opengemini/query_builder.go b/opengemini/query_builder.go index b69d255..451715f 100644 --- a/opengemini/query_builder.go +++ b/opengemini/query_builder.go @@ -157,23 +157,7 @@ func (q *QueryBuilder) buildExpression(expr Expression) string { func (q *QueryBuilder) buildCondition(cond Condition) string { switch c := cond.(type) { case *ComparisonCondition: - // Map ComparisonOperator to SQL symbols - var operator string - switch c.Operator { - case Equals: - operator = "=" - case NotEquals: - operator = "<>" - case GreaterThan: - operator = ">" - case LessThan: - operator = "<" - case GreaterThanOrEquals: - operator = ">=" - case LessThanOrEquals: - operator = "<=" - } - return fmt.Sprintf(`"%s" %s '%v'`, c.Column, operator, c.Value) + return fmt.Sprintf(`"%s" %s '%v'`, c.Column, c.Operator, c.Value) case *CompositeCondition: var parts []string for _, condition := range c.Conditions { diff --git a/opengemini/query_condition.go b/opengemini/query_condition.go index 99e3742..21e4759 100644 --- a/opengemini/query_condition.go +++ b/opengemini/query_condition.go @@ -1,5 +1,7 @@ package opengemini +import "fmt" + type Condition interface{} type ComparisonCondition struct { @@ -8,6 +10,10 @@ type ComparisonCondition struct { Value interface{} } +func (cc *ComparisonCondition) string() string { + return fmt.Sprintf("%s %s %v", cc.Column, cc.Operator, cc.Value) +} + func NewComparisonCondition(column string, operator ComparisonOperator, value interface{}) *ComparisonCondition { return &ComparisonCondition{ Column: column, diff --git a/opengemini/query_operator.go b/opengemini/query_operator.go index d402a20..40e76cb 100644 --- a/opengemini/query_operator.go +++ b/opengemini/query_operator.go @@ -3,12 +3,14 @@ package opengemini type ComparisonOperator string const ( - Equals ComparisonOperator = "EQUALS" - NotEquals ComparisonOperator = "NOT_EQUALS" - GreaterThan ComparisonOperator = "GREATER_THAN" - LessThan ComparisonOperator = "LESS_THAN" - GreaterThanOrEquals ComparisonOperator = "GREATER_THAN_OR_EQUALS" - LessThanOrEquals ComparisonOperator = "LESS_THAN_OR_EQUALS" + Equals ComparisonOperator = "=" + NotEquals ComparisonOperator = "<>" + GreaterThan ComparisonOperator = ">" + LessThan ComparisonOperator = "<" + GreaterThanOrEquals ComparisonOperator = ">=" + LessThanOrEquals ComparisonOperator = "<=" + Match ComparisonOperator = "=~" + NotMatch ComparisonOperator = "!~" ) type LogicalOperator string diff --git a/opengemini/test_util.go b/opengemini/test_util.go index ae93bff..c7123e9 100644 --- a/opengemini/test_util.go +++ b/opengemini/test_util.go @@ -8,7 +8,7 @@ import ( func testDefaultClient(t *testing.T) Client { return testNewClient(t, &Config{ Addresses: []*Address{{ - Host: "localhost", + Host: "192.168.3.16", Port: 8086, }}, })