diff --git a/db/prepared.go b/db/prepared.go index 727662c7..b0b66979 100644 --- a/db/prepared.go +++ b/db/prepared.go @@ -6,9 +6,12 @@ import ( "fmt" "path/filepath" "strings" + "time" //"github.com/stephenafamo/bob" //"github.com/stephenafamo/bob/dialect/psql" + fslayer "github.com/Gleipnir-Technology/arcgis-go/fieldseeker/layer" + "github.com/google/uuid" "github.com/rs/zerolog/log" "github.com/stephenafamo/bob" "github.com/stephenafamo/bob/dialect/psql" @@ -22,6 +25,7 @@ var sqlFiles embed.FS // against the provided database connection. This is intended for // preparing statements that will be used later. func prepareStatements(ctx context.Context) error { + return nil // Get a list of all embedded SQL files entries, err := sqlFiles.ReadDir("prepared_functions") if err != nil { @@ -63,7 +67,7 @@ func prepareStatements(ctx context.Context) error { return nil } -func TestPreparedQuery(ctx context.Context) error { +func TestPreparedQueryOld(ctx context.Context) error { type Skn struct { Result int } @@ -73,21 +77,225 @@ func TestPreparedQuery(ctx context.Context) error { if err != nil { return fmt.Errorf("Failed to exectue test function: %w", err) } - /*insert_id, err := result.LastInsertId() - if err != nil { - log.Error().Err(err).Msg("failed insert id") - return fmt.Errorf("Failed to get insert ID: %w", err) - }*/ - /* - rows_affected, err := result.RowsAffected() - if err != nil { - log.Error().Err(err).Msg("failed rows affected") - return fmt.Errorf("Failed to get rows affected: %w", err) - } - */ - //log.Info().Int64("insert id", insert_id).Int64("rows", rows_affected).Msg("bah") - //log.Info().Int64("rows", rows_affected).Msg("got rows") log.Info().Int("value", result.Result).Msg("got result") return nil } +func TestPreparedQuery(ctx context.Context, row *fslayer.RodentLocation) error { + q := queryStoredProcedure("fieldseeker.insert_rodentlocation", + Uint(row.ObjectID), + String(row.LocationName), + String(row.Zone), + String(row.Zone2), + Enum(row.Habitat), + Enum(row.Priority), + Enum(row.Usetype), + Enum(row.Active), + String(row.Description), + String(row.Accessdesc), + String(row.Comments), + Enum(row.Symbology), + String(row.ExternalID), + Timestamp(row.Nextactiondatescheduled), + Int32(row.Locationnumber), + Timestamp(row.LastInspectionDate), + String(row.LastInspectionSpecies), + String(row.LastInspectionAction), + String(row.LastInspectionConditions), + String(row.LastInspectionRodentEvidence), + UUID(row.GlobalID), + String(row.CreatedUser), + Timestamp(row.CreatedDate), + String(row.LastEditedUser), + Timestamp(row.LastEditedDate), + Timestamp(row.CreationDate), + String(row.Creator), + Timestamp(row.EditDate), + String(row.Editor), + String(row.Jurisdiction), + ) + type InsertResultRow struct { + Added bool `db:"row_inserted"` + Version int `db:"version_num"` + } + type InsertResult struct { + //Row InsertResultRow `db:"insert_rodentlocation"` + Row string `db:"insert_rodentlocation"` + } + query := psql.RawQuery(q) + log.Info().Str("query", q).Msg("querying") + result, err := bob.One[InsertResult](ctx, PGInstance.BobDB, query, scan.StructMapper[InsertResult]()) + if err != nil { + return fmt.Errorf("Failed to execute test function: %w", err) + } + //log.Info().Int("version", result.NextVersion).Msg("got result") + //log.Info().Bool("added", result.Row.Added).Int("version", result.Row.Version).Msg("done") + log.Info().Str("row", result.Row).Msg("done") + + return nil +} + +// SqlParam is a generic struct that wraps a parameter with its SQL representation +type SqlParam interface { + ToSql() string +} + +// StringParam wraps a string parameter +type StringParam string + +func (p StringParam) ToSql() string { + escapedStr := strings.ReplaceAll(string(p), "'", "''") + return fmt.Sprintf("'%s'", escapedStr) +} + +// IntParam wraps an int parameter +type IntParam int64 + +func (p IntParam) ToSql() string { + return fmt.Sprintf("%d", p) +} + +// UintParam wraps a uint parameter +type UintParam uint64 + +func (p UintParam) ToSql() string { + return fmt.Sprintf("%d", p) +} + +type UUIDParam string + +func (p UUIDParam) ToSql() string { + return fmt.Sprintf("'%s'", p) +} + +// FloatParam wraps a float parameter +type FloatParam float64 + +func (p FloatParam) ToSql() string { + return fmt.Sprintf("%f", p) +} + +// BoolParam wraps a boolean parameter +type BoolParam bool + +func (p BoolParam) ToSql() string { + return fmt.Sprintf("%t", p) +} + +// EnumParam wraps a PostgreSQL enum parameter +type EnumParam string + +func (p EnumParam) ToSql() string { + escapedStr := strings.ReplaceAll(string(p), "'", "''") + return fmt.Sprintf("'%s'", escapedStr) +} + +// NullParam represents a NULL value +type NullParam struct{} + +func (NullParam) ToSql() string { + return "NULL" +} + +// Convenience functions to create typed parameters +func String(s string) StringParam { + return StringParam(s) +} + +type Stringable interface { + String() string +} + +func Enum(e Stringable) EnumParam { + return EnumParam(e.String()) +} +func Int(i int) IntParam { + return IntParam(i) +} +func Int32(i int32) IntParam { + return IntParam(i) +} +func Int64(i int64) IntParam { + return IntParam(i) +} + +// Timestamp creates a PostgreSQL TIMESTAMP WITHOUT TIME ZONE parameter +func Timestamp(t time.Time) TimestampParam { + return TimestampParam(t) +} + +// Timestamptz creates a PostgreSQL TIMESTAMP WITH TIME ZONE parameter +func Timestamptz(t time.Time) TimestamptzParam { + return TimestamptzParam(t) +} + +func Uint(u uint) UintParam { + return UintParam(u) +} +func Uint64(u uint64) UintParam { + return UintParam(u) +} +func UUID(u uuid.UUID) UUIDParam { + return UUIDParam(u.String()) +} + +func Float(f float64) FloatParam { + return FloatParam(f) +} + +func Bool(b bool) BoolParam { + return BoolParam(b) +} + +func Null() NullParam { + return NullParam{} +} + +// TimestampParam wraps a time.Time parameter for PostgreSQL TIMESTAMP WITHOUT TIME ZONE +type TimestampParam time.Time + +func (p TimestampParam) ToSql() string { + // Format as PostgreSQL timestamp without timezone + // The format string is based on PostgreSQL's expected format + t := time.Time(p) + return fmt.Sprintf("'%s'::timestamp", t.Format("2006-01-02 15:04:05.999999")) +} + +// TimestamptzParam wraps a time.Time parameter for PostgreSQL TIMESTAMP WITH TIME ZONE +type TimestamptzParam time.Time + +func (p TimestamptzParam) ToSql() string { + // Format as PostgreSQL timestamp with timezone + t := time.Time(p) + return fmt.Sprintf("'%s'::timestamptz", t.Format("2006-01-02 15:04:05.999999-07:00")) +} + +func queryStoredProcedure(procedure string, params ...SqlParam) string { + if len(params) == 0 { + return fmt.Sprintf("SELECT %s()", procedure) + } + + // Convert each parameter to its SQL representation + paramStrings := make([]string, len(params)) + for i, param := range params { + paramStrings[i] = param.ToSql() + } + + // Join parameters and return the execute statement + return fmt.Sprintf("SELECT %s(%s)", procedure, strings.Join(paramStrings, ", ")) +} + +func executeFunction(functionName string, params ...SqlParam) string { + if len(params) == 0 { + return fmt.Sprintf("EXECUTE %s()", functionName) + } + + // Convert each parameter to its SQL representation + paramStrings := make([]string, len(params)) + for i, param := range params { + paramStrings[i] = param.ToSql() + } + + // Join parameters and return the execute statement + return fmt.Sprintf("EXECUTE %s(%s)", functionName, strings.Join(paramStrings, ", ")) +}