package exampleservice import ( "context" "time" "github.com/vingarcia/ksql" "github.com/vingarcia/ksql/nullable" ) // UsersTable informs ksql that the ID column is named "id" var UsersTable = ksql.NewTable("users", "id") // UserEntity represents a domain user, // the pointer fields represent optional fields that // might not be present in some requests. // // Its recommended that this struct contains // one field for each database column, // so you can write generic queries like `SELECT * FROM users`. // // If this is not the case, it might be a good idea // to create a DTO struct to receive select queries. type UserEntity struct { ID int `ksql:"id"` Name *string `ksql:"name"` Age *int `ksql:"age"` Score *int `ksql:"score"` LastPayment time.Time `ksql:"last_payment"` Address *Address `ksql:"address,json"` } // Address contains the user's address type Address struct { AddrLines []string `json:"addr_lines"` City string `json:"city"` State string `json:"state"` Country string `json:"country"` } // Service ... type Service struct { db ksql.Provider streamChunkSize int } // NewUserService ... func NewUserService(db ksql.Provider) Service { return Service{ db: db, streamChunkSize: 100, } } // CreateUser ... func (s Service) CreateUser(ctx context.Context, u UserEntity) error { return s.db.Insert(ctx, UsersTable, &u) } // UpdateUserScore update the user score adding scoreChange with the current // user score. Defaults to 0 if not set. func (s Service) UpdateUserScore(ctx context.Context, uID int, scoreChange int) error { var scoreRow struct { Score int `ksql:"score"` } err := s.db.QueryOne(ctx, &scoreRow, "SELECT score FROM users WHERE id = ?", uID) if err != nil { return err } return s.db.Patch(ctx, UsersTable, &UserEntity{ ID: uID, Score: nullable.Int(scoreRow.Score + scoreChange), }) } // ListUsers returns a page of users func (s Service) ListUsers(ctx context.Context, offset, limit int) (total int, users []UserEntity, err error) { var countRow struct { Count int `ksql:"count"` } err = s.db.QueryOne(ctx, &countRow, "SELECT count(*) as count FROM users") if err != nil { return 0, nil, err } return countRow.Count, users, s.db.Query(ctx, &users, "SELECT * FROM users OFFSET ? LIMIT ?", offset, limit) } // StreamAllUsers sends all users from the database to an external client // // Note: This method is unusual, but so are the use-cases for the QueryChunks function. // In most cases you should just use the Query or QueryOne functions and use the QueryChunks // function only when the ammount of data loaded might exceed the available memory and/or // when you can't put an upper limit on the number of values returned. func (s Service) StreamAllUsers(ctx context.Context, sendUser func(u UserEntity) error) error { return s.db.QueryChunks(ctx, ksql.ChunkParser{ Query: "SELECT * FROM users", Params: []interface{}{}, ChunkSize: s.streamChunkSize, ForEachChunk: func(users []UserEntity) error { for _, user := range users { err := sendUser(user) if err != nil { // This will abort the QueryChunks loop and return this error return err } } return nil }, }) } // DeleteUser deletes a user by its ID func (s Service) DeleteUser(ctx context.Context, uID int) error { return s.db.Delete(ctx, UsersTable, uID) }