875 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			875 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Go
		
	
	
	
/*
 | 
						|
 * Copyright 2025 coze-dev Authors
 | 
						|
 *
 | 
						|
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
 * you may not use this file except in compliance with the License.
 | 
						|
 * You may obtain a copy of the License at
 | 
						|
 *
 | 
						|
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 *
 | 
						|
 * Unless required by applicable law or agreed to in writing, software
 | 
						|
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
 * See the License for the specific language governing permissions and
 | 
						|
 * limitations under the License.
 | 
						|
 */
 | 
						|
 | 
						|
package service
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/stretchr/testify/assert"
 | 
						|
	"go.uber.org/mock/gomock"
 | 
						|
	"gorm.io/driver/mysql"
 | 
						|
	"gorm.io/gorm"
 | 
						|
 | 
						|
	"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/database"
 | 
						|
	"github.com/coze-dev/coze-studio/backend/api/model/table"
 | 
						|
	entity2 "github.com/coze-dev/coze-studio/backend/domain/memory/database/entity"
 | 
						|
	"github.com/coze-dev/coze-studio/backend/domain/memory/database/internal/dal"
 | 
						|
	"github.com/coze-dev/coze-studio/backend/domain/memory/database/repository"
 | 
						|
	"github.com/coze-dev/coze-studio/backend/infra/contract/rdb"
 | 
						|
	rdb2 "github.com/coze-dev/coze-studio/backend/infra/impl/rdb"
 | 
						|
	mock "github.com/coze-dev/coze-studio/backend/internal/mock/infra/contract/idgen"
 | 
						|
	storageMock "github.com/coze-dev/coze-studio/backend/internal/mock/infra/contract/storage"
 | 
						|
	"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
 | 
						|
	"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
 | 
						|
)
 | 
						|
 | 
						|
func setupTestEnv(t *testing.T) (*gorm.DB, rdb.RDB, *mock.MockIDGenerator, repository.DraftDAO, repository.OnlineDAO, Database) {
 | 
						|
	dsn := "root:root@tcp(127.0.0.1:3306)/opencoze?charset=utf8mb4&parseTime=True&loc=Local"
 | 
						|
	if os.Getenv("CI_JOB_NAME") != "" {
 | 
						|
		dsn = strings.ReplaceAll(dsn, "127.0.0.1", "mysql")
 | 
						|
	}
 | 
						|
	gormDB, err := gorm.Open(mysql.Open(dsn))
 | 
						|
	assert.NoError(t, err)
 | 
						|
 | 
						|
	ctrl := gomock.NewController(t)
 | 
						|
	idGen := mock.NewMockIDGenerator(ctrl)
 | 
						|
 | 
						|
	baseID := time.Now().UnixNano()
 | 
						|
	idGen.EXPECT().GenID(gomock.Any()).DoAndReturn(func(ctx context.Context) (int64, error) {
 | 
						|
		id := baseID
 | 
						|
		baseID++
 | 
						|
		return id, nil
 | 
						|
	}).AnyTimes()
 | 
						|
 | 
						|
	idGen.EXPECT().GenMultiIDs(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, count int) ([]int64, error) {
 | 
						|
		ids := make([]int64, count)
 | 
						|
		for i := 0; i < count; i++ {
 | 
						|
			ids[i] = baseID
 | 
						|
			baseID++
 | 
						|
		}
 | 
						|
		return ids, nil
 | 
						|
	}).AnyTimes()
 | 
						|
 | 
						|
	rdbService := rdb2.NewService(gormDB, idGen)
 | 
						|
	draftDAO := dal.NewDraftDatabaseDAO(gormDB, idGen)
 | 
						|
	onlineDAO := dal.NewOnlineDatabaseDAO(gormDB, idGen)
 | 
						|
 | 
						|
	mockStorage := storageMock.NewMockStorage(ctrl)
 | 
						|
	mockStorage.EXPECT().GetObjectUrl(gomock.Any(), gomock.Any()).Return("URL_ADDRESS", nil).AnyTimes()
 | 
						|
	mockStorage.EXPECT().GetObject(gomock.Any(), gomock.Any()).Return([]byte("test text"), nil).AnyTimes()
 | 
						|
	mockStorage.EXPECT().PutObject(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
 | 
						|
 | 
						|
	dbService := NewService(rdbService, gormDB, idGen, mockStorage, nil)
 | 
						|
 | 
						|
	return gormDB, rdbService, idGen, draftDAO, onlineDAO, dbService
 | 
						|
}
 | 
						|
 | 
						|
func cleanupTestEnv(t *testing.T, db *gorm.DB, additionalTables ...string) {
 | 
						|
	sqlDB, err := db.DB()
 | 
						|
	assert.NoError(t, err)
 | 
						|
 | 
						|
	daosToClean := []string{"online_database_info", "draft_database_info"}
 | 
						|
	for _, tableName := range daosToClean {
 | 
						|
		_, err := sqlDB.Exec(fmt.Sprintf("DELETE FROM `%s` WHERE 1=1", tableName))
 | 
						|
		if err != nil {
 | 
						|
			t.Logf("Failed to clean table %s: %v", tableName, err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	rows, err := sqlDB.Query("SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name LIKE 'table_%'")
 | 
						|
	assert.NoError(t, err, "Failed to query tables")
 | 
						|
	defer rows.Close()
 | 
						|
 | 
						|
	var tablesToDrop []string
 | 
						|
	for rows.Next() {
 | 
						|
		var tableName string
 | 
						|
		if err := rows.Scan(&tableName); err != nil {
 | 
						|
			t.Logf("Error scanning table name: %v", err)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		tablesToDrop = append(tablesToDrop, tableName)
 | 
						|
	}
 | 
						|
 | 
						|
	tablesToDrop = append(tablesToDrop, additionalTables...)
 | 
						|
 | 
						|
	for _, tableName := range tablesToDrop {
 | 
						|
		_, err := sqlDB.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName))
 | 
						|
		if err != nil {
 | 
						|
			t.Logf("Failed to drop table %s: %v", tableName, err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCreateDatabase(t *testing.T) {
 | 
						|
	gormDB, _, _, _, onlineDAO, dbService := setupTestEnv(t)
 | 
						|
	defer cleanupTestEnv(t, gormDB)
 | 
						|
 | 
						|
	req := &CreateDatabaseRequest{
 | 
						|
		Database: &database.Database{
 | 
						|
			SpaceID:   1,
 | 
						|
			CreatorID: 1001,
 | 
						|
 | 
						|
			TableName: "test_db_create",
 | 
						|
			FieldList: []*database.FieldItem{
 | 
						|
				{
 | 
						|
					Name:         "id_custom",
 | 
						|
					Type:         table.FieldItemType_Number,
 | 
						|
					MustRequired: true,
 | 
						|
				},
 | 
						|
				{
 | 
						|
					Name:         "name",
 | 
						|
					Type:         table.FieldItemType_Text,
 | 
						|
					MustRequired: true,
 | 
						|
				},
 | 
						|
				{
 | 
						|
					Name: "score",
 | 
						|
					Type: table.FieldItemType_Float,
 | 
						|
				},
 | 
						|
				{
 | 
						|
					Name: "date",
 | 
						|
					Type: table.FieldItemType_Date,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	resp, err := dbService.CreateDatabase(context.Background(), req)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, resp)
 | 
						|
	assert.NotNil(t, resp.Database)
 | 
						|
 | 
						|
	assert.Equal(t, req.Database.TableName, resp.Database.TableName)
 | 
						|
	assert.Equal(t, req.Database.TableType, resp.Database.TableType)
 | 
						|
	assert.NotEmpty(t, resp.Database.ActualTableName)
 | 
						|
	assert.Len(t, resp.Database.FieldList, 4)
 | 
						|
 | 
						|
	savedDB, err := onlineDAO.Get(context.Background(), resp.Database.ID)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.Equal(t, resp.Database.ID, savedDB.ID)
 | 
						|
	assert.Equal(t, resp.Database.TableName, savedDB.TableName)
 | 
						|
}
 | 
						|
 | 
						|
func TestUpdateDatabase(t *testing.T) {
 | 
						|
	gormDB, _, _, _, onlineDAO, dbService := setupTestEnv(t)
 | 
						|
	defer cleanupTestEnv(t, gormDB)
 | 
						|
 | 
						|
	resp, err := createDatabase(dbService)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, resp)
 | 
						|
 | 
						|
	databaseInfo := &entity2.Database{
 | 
						|
		ID: resp.Database.ID,
 | 
						|
 | 
						|
		SpaceID:   1,
 | 
						|
		CreatorID: 1001,
 | 
						|
		TableName: "test_db_update",
 | 
						|
		TableType: ptr.Of(table.TableType_OnlineTable),
 | 
						|
		FieldList: []*database.FieldItem{
 | 
						|
			{
 | 
						|
				Name:         "age",
 | 
						|
				Type:         table.FieldItemType_Float,
 | 
						|
				MustRequired: true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	updateReq := &UpdateDatabaseRequest{
 | 
						|
		Database: databaseInfo,
 | 
						|
	}
 | 
						|
 | 
						|
	res, err := dbService.UpdateDatabase(context.Background(), updateReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, res)
 | 
						|
 | 
						|
	updatedDB, err := onlineDAO.Get(context.Background(), databaseInfo.ID)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.Len(t, updatedDB.FieldList, 1)
 | 
						|
}
 | 
						|
 | 
						|
func TestDeleteDatabase(t *testing.T) {
 | 
						|
	gormDB, _, _, draftDAO, onlineDAO, dbService := setupTestEnv(t)
 | 
						|
	defer cleanupTestEnv(t, gormDB)
 | 
						|
 | 
						|
	resp, err := createDatabase(dbService)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, resp)
 | 
						|
 | 
						|
	deleteReq := &DeleteDatabaseRequest{
 | 
						|
		ID: resp.Database.ID,
 | 
						|
	}
 | 
						|
 | 
						|
	err = dbService.DeleteDatabase(context.Background(), deleteReq)
 | 
						|
 | 
						|
	assert.NoError(t, err)
 | 
						|
 | 
						|
	_, err = draftDAO.Get(context.Background(), resp.Database.ID)
 | 
						|
	assert.Error(t, err)
 | 
						|
	assert.Contains(t, err.Error(), "not found")
 | 
						|
 | 
						|
	_, err = onlineDAO.Get(context.Background(), resp.Database.ID)
 | 
						|
	assert.Error(t, err)
 | 
						|
	assert.Contains(t, err.Error(), "not found")
 | 
						|
}
 | 
						|
 | 
						|
func TestListDatabase(t *testing.T) {
 | 
						|
	gormDB, _, _, _, _, dbService := setupTestEnv(t)
 | 
						|
	defer cleanupTestEnv(t, gormDB)
 | 
						|
 | 
						|
	for i := 0; i < 3; i++ {
 | 
						|
		resp, err := createDatabase(dbService)
 | 
						|
		assert.NoError(t, err)
 | 
						|
		assert.NotNil(t, resp)
 | 
						|
	}
 | 
						|
 | 
						|
	spaceID := int64(1)
 | 
						|
	tableType := table.TableType_OnlineTable
 | 
						|
	listReq := &ListDatabaseRequest{
 | 
						|
		SpaceID:   &spaceID,
 | 
						|
		TableType: tableType,
 | 
						|
		Limit:     2,
 | 
						|
	}
 | 
						|
	resp, err := dbService.ListDatabase(context.Background(), listReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, resp)
 | 
						|
	assert.GreaterOrEqual(t, len(resp.Databases), 2)
 | 
						|
 | 
						|
	listReq = &ListDatabaseRequest{
 | 
						|
		SpaceID:   &spaceID,
 | 
						|
		TableType: tableType,
 | 
						|
		Limit:     2,
 | 
						|
		Offset:    2,
 | 
						|
	}
 | 
						|
	resp, err = dbService.ListDatabase(context.Background(), listReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, resp)
 | 
						|
	assert.GreaterOrEqual(t, len(resp.Databases), 1)
 | 
						|
}
 | 
						|
 | 
						|
func createDatabase(dbService Database) (*CreateDatabaseResponse, error) {
 | 
						|
	req := &CreateDatabaseRequest{
 | 
						|
		Database: &entity2.Database{
 | 
						|
			SpaceID:   1,
 | 
						|
			CreatorID: 1001,
 | 
						|
 | 
						|
			TableName: "test_db_table_01",
 | 
						|
			FieldList: []*database.FieldItem{
 | 
						|
				{
 | 
						|
					Name:         "id_custom",
 | 
						|
					Type:         table.FieldItemType_Number,
 | 
						|
					MustRequired: true,
 | 
						|
				},
 | 
						|
				{
 | 
						|
					Name:         "name",
 | 
						|
					Type:         table.FieldItemType_Text,
 | 
						|
					MustRequired: true,
 | 
						|
				},
 | 
						|
				{
 | 
						|
					Name: "score",
 | 
						|
					Type: table.FieldItemType_Float,
 | 
						|
				},
 | 
						|
				{
 | 
						|
					Name: "date",
 | 
						|
					Type: table.FieldItemType_Date,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	return dbService.CreateDatabase(context.Background(), req)
 | 
						|
}
 | 
						|
 | 
						|
func TestCRUDDatabaseRecord(t *testing.T) {
 | 
						|
	gormDB, _, _, _, _, dbService := setupTestEnv(t)
 | 
						|
	defer cleanupTestEnv(t, gormDB)
 | 
						|
 | 
						|
	resp, err := createDatabase(dbService)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, resp)
 | 
						|
 | 
						|
	addRecordReq := &AddDatabaseRecordRequest{
 | 
						|
		DatabaseID: resp.Database.ID,
 | 
						|
		TableType:  table.TableType_OnlineTable,
 | 
						|
		UserID:     1001,
 | 
						|
		Records: []map[string]string{
 | 
						|
			{
 | 
						|
				"id_custom": "1",
 | 
						|
				"name":      "John Doe",
 | 
						|
				"score":     "80.5",
 | 
						|
				"date":      "2025-01-01 00:00:00",
 | 
						|
			},
 | 
						|
			{
 | 
						|
				"id_custom":  "2",
 | 
						|
				"name":       "Jane Smith",
 | 
						|
				"score":      "90.5",
 | 
						|
				"date":       "2025-01-01 01:00:00",
 | 
						|
				"bstudio_id": "1",
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	err = dbService.AddDatabaseRecord(context.Background(), addRecordReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
 | 
						|
	listRecordReq := &ListDatabaseRecordRequest{
 | 
						|
		DatabaseID: resp.Database.ID,
 | 
						|
		TableType:  table.TableType_OnlineTable,
 | 
						|
		UserID:     1001,
 | 
						|
		Limit:      50,
 | 
						|
	}
 | 
						|
 | 
						|
	listResp, err := dbService.ListDatabaseRecord(context.Background(), listRecordReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, listResp)
 | 
						|
	assert.True(t, len(listResp.Records) == 2)
 | 
						|
 | 
						|
	foundJohn := false
 | 
						|
	foundSmith := false
 | 
						|
	bsID := ""
 | 
						|
	for _, record := range listResp.Records {
 | 
						|
		if record["name"] == "John Doe" && record["score"] == "80.5" {
 | 
						|
			foundJohn = true
 | 
						|
			bsID = record[database.DefaultIDColName]
 | 
						|
		}
 | 
						|
		if record["name"] == "Jane Smith" && record["score"] == "90.5" {
 | 
						|
			foundSmith = true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	assert.True(t, foundJohn, "John Doe record not found")
 | 
						|
	assert.True(t, foundSmith, "Jane Smith record not found")
 | 
						|
 | 
						|
	updateRecordReq := &UpdateDatabaseRecordRequest{
 | 
						|
		DatabaseID: resp.Database.ID,
 | 
						|
		TableType:  table.TableType_OnlineTable,
 | 
						|
		UserID:     1001,
 | 
						|
		Records: []map[string]string{
 | 
						|
			{
 | 
						|
				database.DefaultIDColName: bsID,
 | 
						|
				"name":                    "John Updated",
 | 
						|
				"score":                   "90",
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	err = dbService.UpdateDatabaseRecord(context.Background(), updateRecordReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
 | 
						|
	listReq := &ListDatabaseRecordRequest{
 | 
						|
		DatabaseID: resp.Database.ID,
 | 
						|
		TableType:  table.TableType_OnlineTable,
 | 
						|
		Limit:      50,
 | 
						|
		UserID:     1001,
 | 
						|
	}
 | 
						|
	listRespAfterUpdate, err := dbService.ListDatabaseRecord(context.Background(), listReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, listRespAfterUpdate)
 | 
						|
	assert.True(t, len(listRespAfterUpdate.Records) > 0)
 | 
						|
 | 
						|
	foundJohnUpdate := false
 | 
						|
	for _, record := range listRespAfterUpdate.Records {
 | 
						|
		if record[database.DefaultIDColName] == bsID {
 | 
						|
			foundJohnUpdate = true
 | 
						|
			assert.Equal(t, "90", record["score"])
 | 
						|
		}
 | 
						|
	}
 | 
						|
	assert.True(t, foundJohnUpdate, "John Doe update record not found")
 | 
						|
 | 
						|
	deleteRecordReq := &DeleteDatabaseRecordRequest{
 | 
						|
		DatabaseID: resp.Database.ID,
 | 
						|
		TableType:  table.TableType_OnlineTable,
 | 
						|
		UserID:     1001,
 | 
						|
		Records: []map[string]string{
 | 
						|
			{
 | 
						|
				database.DefaultIDColName: bsID,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	err = dbService.DeleteDatabaseRecord(context.Background(), deleteRecordReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
 | 
						|
	listRecordAfterDeleteReq := &ListDatabaseRecordRequest{
 | 
						|
		DatabaseID: resp.Database.ID,
 | 
						|
		TableType:  table.TableType_OnlineTable,
 | 
						|
		Limit:      50,
 | 
						|
		UserID:     1001,
 | 
						|
	}
 | 
						|
	listRespAfterDelete, err := dbService.ListDatabaseRecord(context.Background(), listRecordAfterDeleteReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, listRespAfterDelete)
 | 
						|
	assert.Equal(t, len(listRespAfterDelete.Records), 1)
 | 
						|
}
 | 
						|
 | 
						|
func TestExecuteSQLWithOperations(t *testing.T) {
 | 
						|
	gormDB, _, _, _, _, dbService := setupTestEnv(t)
 | 
						|
	defer cleanupTestEnv(t, gormDB)
 | 
						|
 | 
						|
	resp, err := createDatabase(dbService)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, resp)
 | 
						|
 | 
						|
	fieldMap := slices.ToMap(resp.Database.FieldList, func(e *database.FieldItem) (string, *database.FieldItem) {
 | 
						|
		return e.Name, e
 | 
						|
	})
 | 
						|
 | 
						|
	upsertRows := []*database.UpsertRow{
 | 
						|
		{
 | 
						|
			Records: []*database.Record{
 | 
						|
				{
 | 
						|
					FieldId:    strconv.FormatInt(fieldMap["id_custom"].AlterID, 10),
 | 
						|
					FieldValue: "?",
 | 
						|
				},
 | 
						|
				{
 | 
						|
					FieldId:    strconv.FormatInt(fieldMap["name"].AlterID, 10),
 | 
						|
					FieldValue: "?",
 | 
						|
				},
 | 
						|
				{
 | 
						|
					FieldId:    strconv.FormatInt(fieldMap["score"].AlterID, 10),
 | 
						|
					FieldValue: "?",
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Records: []*database.Record{
 | 
						|
				{
 | 
						|
					FieldId:    strconv.FormatInt(fieldMap["id_custom"].AlterID, 10),
 | 
						|
					FieldValue: "?",
 | 
						|
				},
 | 
						|
				{
 | 
						|
					FieldId:    strconv.FormatInt(fieldMap["name"].AlterID, 10),
 | 
						|
					FieldValue: "?",
 | 
						|
				},
 | 
						|
				{
 | 
						|
					FieldId:    strconv.FormatInt(fieldMap["score"].AlterID, 10),
 | 
						|
					FieldValue: "?",
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	executeInsertReq := &ExecuteSQLRequest{
 | 
						|
		DatabaseID:  resp.Database.ID,
 | 
						|
		TableType:   table.TableType_OnlineTable,
 | 
						|
		OperateType: database.OperateType_Insert,
 | 
						|
		UpsertRows:  upsertRows,
 | 
						|
		SQLParams: []*database.SQLParamVal{
 | 
						|
			{
 | 
						|
				Value: ptr.Of("111"),
 | 
						|
			},
 | 
						|
			{
 | 
						|
				Value: ptr.Of("Alice"),
 | 
						|
			},
 | 
						|
			{
 | 
						|
				Value: ptr.Of("85.5"),
 | 
						|
			},
 | 
						|
			{
 | 
						|
				Value: ptr.Of("112"),
 | 
						|
			},
 | 
						|
			{
 | 
						|
				Value: ptr.Of("Bob"),
 | 
						|
			},
 | 
						|
			{
 | 
						|
				Value: ptr.Of("90.5"),
 | 
						|
			},
 | 
						|
		},
 | 
						|
		UserID:  "1001",
 | 
						|
		SpaceID: 1,
 | 
						|
	}
 | 
						|
 | 
						|
	insertResp, err := dbService.ExecuteSQL(context.Background(), executeInsertReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, insertResp)
 | 
						|
	assert.NotNil(t, insertResp.RowsAffected)
 | 
						|
	assert.Equal(t, 2, len(insertResp.Records))
 | 
						|
	assert.Equal(t, 1, len(insertResp.Records[0]))
 | 
						|
	assert.Equal(t, int64(2), *insertResp.RowsAffected)
 | 
						|
 | 
						|
	limit := int64(10)
 | 
						|
	selectFields := &database.SelectFieldList{
 | 
						|
		FieldID: []string{strconv.FormatInt(fieldMap["name"].AlterID, 10), strconv.FormatInt(fieldMap["score"].AlterID, 10)},
 | 
						|
	}
 | 
						|
 | 
						|
	executeSelectReq := &ExecuteSQLRequest{
 | 
						|
		DatabaseID:  resp.Database.ID,
 | 
						|
		TableType:   table.TableType_OnlineTable,
 | 
						|
		OperateType: database.OperateType_Select,
 | 
						|
		// SelectFieldList: selectFields,
 | 
						|
		Limit:   &limit,
 | 
						|
		UserID:  "1001",
 | 
						|
		SpaceID: 1,
 | 
						|
		OrderByList: []database.OrderBy{
 | 
						|
			{
 | 
						|
				Field:     "id_custom",
 | 
						|
				Direction: table.SortDirection_Desc,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	selectResp, err := dbService.ExecuteSQL(context.Background(), executeSelectReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, selectResp)
 | 
						|
	assert.NotNil(t, selectResp.Records)
 | 
						|
	assert.True(t, len(selectResp.Records) == 2)
 | 
						|
	assert.Equal(t, string(selectResp.Records[0]["name"].([]uint8)), "Bob")
 | 
						|
	assert.NotNil(t, selectResp.Records[0][database.DefaultUidDisplayColName])
 | 
						|
	assert.NotNil(t, selectResp.Records[0][database.DefaultIDDisplayColName])
 | 
						|
	assert.NotNil(t, selectResp.Records[0][database.DefaultCreateTimeDisplayColName])
 | 
						|
 | 
						|
	executeNotNullSelectReq := &ExecuteSQLRequest{
 | 
						|
		DatabaseID:      resp.Database.ID,
 | 
						|
		TableType:       table.TableType_OnlineTable,
 | 
						|
		OperateType:     database.OperateType_Select,
 | 
						|
		SelectFieldList: selectFields,
 | 
						|
		Limit:           &limit,
 | 
						|
		UserID:          "1001",
 | 
						|
		SpaceID:         1,
 | 
						|
		OrderByList: []database.OrderBy{
 | 
						|
			{
 | 
						|
				Field:     "id_custom",
 | 
						|
				Direction: table.SortDirection_Desc,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		Condition: &database.ComplexCondition{
 | 
						|
			Conditions: []*database.Condition{
 | 
						|
				{
 | 
						|
					Left:      "name",
 | 
						|
					Operation: database.Operation_IS_NOT_NULL,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			Logic: database.Logic_And,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	selectNotNullResp, err := dbService.ExecuteSQL(context.Background(), executeNotNullSelectReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, selectNotNullResp)
 | 
						|
	assert.NotNil(t, selectNotNullResp.Records)
 | 
						|
	assert.True(t, len(selectNotNullResp.Records) == 2)
 | 
						|
	assert.Equal(t, string(selectNotNullResp.Records[0]["name"].([]uint8)), "Bob")
 | 
						|
 | 
						|
	executeNullSelectReq := &ExecuteSQLRequest{
 | 
						|
		DatabaseID:      resp.Database.ID,
 | 
						|
		TableType:       table.TableType_OnlineTable,
 | 
						|
		OperateType:     database.OperateType_Select,
 | 
						|
		SelectFieldList: selectFields,
 | 
						|
		Limit:           &limit,
 | 
						|
		UserID:          "1001",
 | 
						|
		SpaceID:         1,
 | 
						|
		OrderByList: []database.OrderBy{
 | 
						|
			{
 | 
						|
				Field:     "id_custom",
 | 
						|
				Direction: table.SortDirection_Desc,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		Condition: &database.ComplexCondition{
 | 
						|
			Conditions: []*database.Condition{
 | 
						|
				{
 | 
						|
					Left:      "name",
 | 
						|
					Operation: database.Operation_IS_NULL,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			Logic: database.Logic_And,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	selectNullResp, err := dbService.ExecuteSQL(context.Background(), executeNullSelectReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, selectNullResp)
 | 
						|
	assert.True(t, len(selectNullResp.Records) == 0)
 | 
						|
 | 
						|
	executeINSelectReq := &ExecuteSQLRequest{
 | 
						|
		DatabaseID:      resp.Database.ID,
 | 
						|
		TableType:       table.TableType_OnlineTable,
 | 
						|
		OperateType:     database.OperateType_Select,
 | 
						|
		SelectFieldList: selectFields,
 | 
						|
		Limit:           &limit,
 | 
						|
		UserID:          "1001",
 | 
						|
		SpaceID:         1,
 | 
						|
		OrderByList: []database.OrderBy{
 | 
						|
			{
 | 
						|
				Field:     "id_custom",
 | 
						|
				Direction: table.SortDirection_Desc,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		SQLParams: []*database.SQLParamVal{
 | 
						|
			{
 | 
						|
				Value: ptr.Of("Alice"),
 | 
						|
			},
 | 
						|
			{
 | 
						|
				Value: ptr.Of("Bob"),
 | 
						|
			},
 | 
						|
		},
 | 
						|
		Condition: &database.ComplexCondition{
 | 
						|
			Conditions: []*database.Condition{
 | 
						|
				{
 | 
						|
					Left:      "name",
 | 
						|
					Operation: database.Operation_IN,
 | 
						|
					Right:     "(?,?)",
 | 
						|
				},
 | 
						|
			},
 | 
						|
			Logic: database.Logic_And,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	selectINResp, err := dbService.ExecuteSQL(context.Background(), executeINSelectReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, selectINResp)
 | 
						|
	assert.True(t, len(selectINResp.Records) == 2)
 | 
						|
 | 
						|
	updateRows := []*database.UpsertRow{
 | 
						|
		{
 | 
						|
			Records: []*database.Record{
 | 
						|
				{
 | 
						|
					FieldId:    strconv.FormatInt(fieldMap["id_custom"].AlterID, 10),
 | 
						|
					FieldValue: "?",
 | 
						|
				},
 | 
						|
				{
 | 
						|
					FieldId:    strconv.FormatInt(fieldMap["name"].AlterID, 10),
 | 
						|
					FieldValue: "?",
 | 
						|
				},
 | 
						|
				{
 | 
						|
					FieldId:    strconv.FormatInt(fieldMap["score"].AlterID, 10),
 | 
						|
					FieldValue: "?",
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	executeUpdateReq := &ExecuteSQLRequest{
 | 
						|
		DatabaseID:  resp.Database.ID,
 | 
						|
		TableType:   table.TableType_OnlineTable,
 | 
						|
		OperateType: database.OperateType_Update,
 | 
						|
		UpsertRows:  updateRows,
 | 
						|
		Limit:       &limit,
 | 
						|
		SQLParams: []*database.SQLParamVal{
 | 
						|
			{
 | 
						|
				Value: ptr.Of("111"),
 | 
						|
			},
 | 
						|
			{
 | 
						|
				Value: ptr.Of("Alice2"),
 | 
						|
			},
 | 
						|
			{
 | 
						|
				Value: ptr.Of("99"),
 | 
						|
			},
 | 
						|
			{
 | 
						|
				Value: ptr.Of("111"),
 | 
						|
			},
 | 
						|
		},
 | 
						|
		UserID:  "1001",
 | 
						|
		SpaceID: 1,
 | 
						|
		Condition: &database.ComplexCondition{
 | 
						|
			Conditions: []*database.Condition{
 | 
						|
				{
 | 
						|
					Left:      "id_custom",
 | 
						|
					Operation: database.Operation_EQUAL,
 | 
						|
					Right:     "?",
 | 
						|
				},
 | 
						|
			},
 | 
						|
			Logic: database.Logic_And,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	updateResp, err := dbService.ExecuteSQL(context.Background(), executeUpdateReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, updateResp)
 | 
						|
	assert.NotNil(t, updateResp.RowsAffected)
 | 
						|
 | 
						|
	executeDeleteReq := &ExecuteSQLRequest{
 | 
						|
		DatabaseID:  resp.Database.ID,
 | 
						|
		TableType:   table.TableType_OnlineTable,
 | 
						|
		OperateType: database.OperateType_Delete,
 | 
						|
		Limit:       &limit,
 | 
						|
		UserID:      "1001",
 | 
						|
		SpaceID:     1,
 | 
						|
		SQLParams: []*database.SQLParamVal{
 | 
						|
			{
 | 
						|
				Value: ptr.Of("111"),
 | 
						|
			},
 | 
						|
		},
 | 
						|
		Condition: &database.ComplexCondition{
 | 
						|
			Conditions: []*database.Condition{
 | 
						|
				{
 | 
						|
					Left:      "id_custom",
 | 
						|
					Operation: database.Operation_EQUAL,
 | 
						|
					Right:     "?",
 | 
						|
				},
 | 
						|
			},
 | 
						|
			Logic: database.Logic_And,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	dResp, err := dbService.ExecuteSQL(context.Background(), executeDeleteReq)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, dResp)
 | 
						|
	assert.NotNil(t, dResp.RowsAffected)
 | 
						|
 | 
						|
	selectCustom := &ExecuteSQLRequest{
 | 
						|
		DatabaseID:  resp.Database.ID,
 | 
						|
		TableType:   table.TableType_OnlineTable,
 | 
						|
		OperateType: database.OperateType_Custom,
 | 
						|
		Limit:       &limit,
 | 
						|
		UserID:      "1001",
 | 
						|
		SpaceID:     1,
 | 
						|
		SQL:         ptr.Of(fmt.Sprintf("SELECT * FROM %s WHERE score > ?", "test_db_table_01")),
 | 
						|
		SQLParams: []*database.SQLParamVal{
 | 
						|
			{
 | 
						|
				Value: ptr.Of("85"),
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	selectCustomResp, err := dbService.ExecuteSQL(context.Background(), selectCustom)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, selectCustomResp)
 | 
						|
	assert.NotNil(t, selectCustomResp.Records)
 | 
						|
	assert.True(t, len(selectCustomResp.Records) > 0)
 | 
						|
 | 
						|
	// Test custom SQL UPDATE
 | 
						|
	updateCustom := &ExecuteSQLRequest{
 | 
						|
		DatabaseID:  resp.Database.ID,
 | 
						|
		TableType:   table.TableType_OnlineTable,
 | 
						|
		OperateType: database.OperateType_Custom,
 | 
						|
		UserID:      "1001",
 | 
						|
		SpaceID:     1,
 | 
						|
		SQL:         ptr.Of(fmt.Sprintf("UPDATE %s SET name = 'Bob Updated' WHERE id_custom = ?", "test_db_table_01")),
 | 
						|
		SQLParams: []*database.SQLParamVal{
 | 
						|
			{
 | 
						|
				Value: ptr.Of("112"),
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	updateCustomResp, err := dbService.ExecuteSQL(context.Background(), updateCustom)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, updateCustomResp)
 | 
						|
	assert.NotNil(t, updateCustomResp.RowsAffected)
 | 
						|
	assert.Equal(t, *updateCustomResp.RowsAffected, int64(1))
 | 
						|
 | 
						|
	// Test custom SQL DELETE
 | 
						|
	deleteCustom := &ExecuteSQLRequest{
 | 
						|
		DatabaseID:  resp.Database.ID,
 | 
						|
		TableType:   table.TableType_OnlineTable,
 | 
						|
		OperateType: database.OperateType_Custom,
 | 
						|
		UserID:      "1001",
 | 
						|
		SpaceID:     1,
 | 
						|
		SQL:         ptr.Of(fmt.Sprintf("DELETE FROM %s WHERE id_custom = ?", "test_db_table_01")),
 | 
						|
		SQLParams: []*database.SQLParamVal{
 | 
						|
			{
 | 
						|
				Value: ptr.Of("112"),
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	deleteCustomResp, err := dbService.ExecuteSQL(context.Background(), deleteCustom)
 | 
						|
	assert.NoError(t, err)
 | 
						|
	assert.NotNil(t, deleteCustomResp)
 | 
						|
	assert.NotNil(t, deleteCustomResp.RowsAffected)
 | 
						|
	assert.Equal(t, *deleteCustomResp.RowsAffected, int64(1))
 | 
						|
}
 | 
						|
 | 
						|
func TestDeleteDatabaseByAppID(t *testing.T) {
 | 
						|
	gormDB, _, _, draftDAO, onlineDAO, dbService := setupTestEnv(t)
 | 
						|
	defer cleanupTestEnv(t, gormDB)
 | 
						|
 | 
						|
	appID := int64(123456)
 | 
						|
	dbCount := 3
 | 
						|
	var onlineIDs []int64
 | 
						|
	var draftIDs []int64
 | 
						|
	var physicalTables []string
 | 
						|
 | 
						|
	for i := 0; i < dbCount; i++ {
 | 
						|
		dbName := fmt.Sprintf("test_appid_db_%d_%d", appID, i)
 | 
						|
		req := &CreateDatabaseRequest{
 | 
						|
			Database: &entity2.Database{
 | 
						|
				AppID:     appID,
 | 
						|
				SpaceID:   1,
 | 
						|
				CreatorID: 1001,
 | 
						|
				TableName: dbName,
 | 
						|
				FieldList: []*database.FieldItem{
 | 
						|
					{
 | 
						|
						Name:         "id_custom",
 | 
						|
						Type:         table.FieldItemType_Number,
 | 
						|
						MustRequired: true,
 | 
						|
					},
 | 
						|
					{
 | 
						|
						Name:         "name",
 | 
						|
						Type:         table.FieldItemType_Text,
 | 
						|
						MustRequired: true,
 | 
						|
					},
 | 
						|
					{
 | 
						|
						Name: "score",
 | 
						|
						Type: table.FieldItemType_Float,
 | 
						|
					},
 | 
						|
					{
 | 
						|
						Name: "date",
 | 
						|
						Type: table.FieldItemType_Date,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		}
 | 
						|
		resp, err := dbService.CreateDatabase(context.Background(), req)
 | 
						|
		assert.NoError(t, err)
 | 
						|
		assert.NotNil(t, resp)
 | 
						|
		assert.NotNil(t, resp.Database)
 | 
						|
		assert.Equal(t, appID, resp.Database.AppID)
 | 
						|
		assert.NotEmpty(t, resp.Database.ActualTableName)
 | 
						|
		physicalTables = append(physicalTables, resp.Database.ActualTableName)
 | 
						|
		if resp.Database.ID != 0 {
 | 
						|
			onlineIDs = append(onlineIDs, resp.Database.ID)
 | 
						|
		}
 | 
						|
		if resp.Database.DraftID != nil {
 | 
						|
			draftIDs = append(draftIDs, *resp.Database.DraftID)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for _, id := range onlineIDs {
 | 
						|
		_, err := onlineDAO.Get(context.Background(), id)
 | 
						|
		assert.NoError(t, err)
 | 
						|
	}
 | 
						|
	for _, id := range draftIDs {
 | 
						|
		_, err := draftDAO.Get(context.Background(), id)
 | 
						|
		assert.NoError(t, err)
 | 
						|
	}
 | 
						|
 | 
						|
	_, err := dbService.DeleteDatabaseByAppID(context.Background(), &DeleteDatabaseByAppIDRequest{AppID: appID})
 | 
						|
	assert.NoError(t, err)
 | 
						|
 | 
						|
	for _, id := range onlineIDs {
 | 
						|
		_, err := onlineDAO.Get(context.Background(), id)
 | 
						|
		assert.Error(t, err)
 | 
						|
		assert.Contains(t, err.Error(), "not found")
 | 
						|
	}
 | 
						|
	for _, id := range draftIDs {
 | 
						|
		_, err := draftDAO.Get(context.Background(), id)
 | 
						|
		assert.Error(t, err)
 | 
						|
		assert.Contains(t, err.Error(), "not found")
 | 
						|
	}
 | 
						|
 | 
						|
	sqlDB, err := gormDB.DB()
 | 
						|
	assert.NoError(t, err)
 | 
						|
	for _, tableName := range physicalTables {
 | 
						|
		var cnt int
 | 
						|
		err := sqlDB.QueryRow(fmt.Sprintf("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = '%s'", tableName)).Scan(&cnt)
 | 
						|
		assert.NoError(t, err)
 | 
						|
		assert.Equal(t, 0, cnt, "physical table %s should be deleted", tableName)
 | 
						|
	}
 | 
						|
}
 |