diff --git a/docs/contributing/go/sql.md b/docs/contributing/go/sql.md new file mode 100644 index 0000000000..c43ea4a604 --- /dev/null +++ b/docs/contributing/go/sql.md @@ -0,0 +1,94 @@ +# SQL +SigNoz utilizes a relational database to store metadata including organization information, user data and other settings. + +## How to use it? + +The database interface is defined in [SQLStore](/pkg/sqlstore/sqlstore.go). SigNoz leverages the Bun ORM to interact with the underlying database. To access the database instance, use the `BunDBCtx` function. For operations that require transactions across multiple database operations, use the `RunInTxCtx` function. This function embeds a transaction in the context, which propagates through various functions in the callback. + +```go +type Thing struct { + bun.BaseModel + + ID types.Identifiable `bun:",embed"` + SomeColumn string `bun:"some_column"` + TimeAuditable types.TimeAuditable `bun:",embed"` + OrgID string `bun:"org_id"` +} + +func GetThing(ctx context.Context, id string) (*Thing, error) { + thing := new(Thing) + err := sqlstore. + BunDBCtx(ctx). + NewSelect(). + Model(thing). + Where("id = ?", id). + Scan(ctx) + + return thing, err +} + +func CreateThing(ctx context.Context, thing *Thing) error { + return sqlstore. + BunDBCtx(ctx). + NewInsert(). + Model(thing). + Exec(ctx) +} +``` + +> 💡 **Note**: Always use line breaks while working with SQL queries to enhance code readability. + +> 💡 **Note**: Always use the `new` function to create new instances of structs. + +## What are hooks? + +Hooks are user-defined functions that execute before and/or after specific database operations. These hooks are particularly useful for generating telemetry data such as logs, traces, and metrics, providing visibility into database interactions. Hooks are defined in the [SQLStoreHook](/pkg/sqlstore/sqlstore.go) interface. + +## How is the schema designed? + +SigNoz implements a star schema design with the organizations table as the central entity. All other tables link to the organizations table via foreign key constraints on the `org_id` column. This design ensures that every entity within the system is either directly or indirectly associated with an organization. + +```mermaid +erDiagram + ORGANIZATIONS { + string id PK + timestamp created_at + timestamp updated_at + } + ENTITY_A { + string id PK + timestamp created_at + timestamp updated_at + string org_id FK + } + ENTITY_B { + string id PK + timestamp created_at + timestamp updated_at + string org_id FK + } + + ORGANIZATIONS ||--o{ ENTITY_A : contains + ORGANIZATIONS ||--o{ ENTITY_B : contains +``` + +> 💡 **Note**: There are rare exceptions to the above star schema design. Consult with the maintainers before deviating from the above design. + +All tables follow a consistent primary key pattern using a `id` column (referenced by the `types.Identifiable` struct) and include `created_at` and `updated_at` columns (referenced by the `types.TimeAuditable` struct) for audit purposes. + +## How to write migrations? + +For schema migrations, use the [SQLMigration](/pkg/sqlmigration/sqlmigration.go) interface and write the migration in the same package. When creating migrations, adhere to these guidelines: + +- Do not implement **`ON CASCADE` foreign key constraints**. Deletion operations should be handled explicitly in application logic rather than delegated to the database. +- Do not **import types from the types package** in the `sqlmigration` package. Instead, define the required types within the migration package itself. This practice ensures migration stability as the core types evolve over time. +- Do not implement **`Down` migrations**. As the codebase matures, we may introduce this capability, but for now, the `Down` function should remain empty. +- Always write **idempotent** migrations. This means that if the migration is run multiple times, it should not cause an error. +- A migration which is **dependent on the underlying dialect** (sqlite, postgres, etc) should be written as part of the [SQLDialect](/pkg/sqlstore/sqlstore.go) interface. The implementation needs to go in the dialect specific package of the respective database. + +## What should I remember? + +- Use `BunDBCtx` and `RunInTxCtx` to access the database instance and execute transactions respectively. +- While designing new tables, ensure the consistency of `id`, `created_at`, `updated_at` and an `org_id` column with a foreign key constraint to the `organizations` table (unless the table serves as a transitive entity not directly associated with an organization but indirectly associated with one). +- Implement deletion logic in the application rather than relying on cascading deletes in the database. +- While writing migrations, adhere to the guidelines mentioned above.