Documentation
¶
Overview ¶
Package azuretable provides a local, in-memory emulation of Azure Table Storage's REST+JSON/OData wire protocol (table CRUD plus entity insert/get/query/replace/merge/delete, including a hand-written $filter lexer/parser/evaluator), Azurite-compatible enough for unmodified azure-sdk-for-go clients to operate against. See AZURE.md and PARITY.md for scope and known gaps.
The entity CRUD/$filter engine itself lives in pkgs/odatatable (extracted here in AZURE.md section 9's M6 milestone so services/cosmosdb's Table API could import the same engine instead of duplicating it); this package is a thin wire-protocol adapter over it -- HTTP routing/dispatch (handler.go), request/response shaping (table_ops.go/entity_ops.go), and re-exports of pkgs/odatatable's types/functions/errors under their historical azuretable-local names (models.go, errors.go, store.go) so this package's own public API is unaffected by the extraction.
Unlike services/azurequeue and services/azureblob, this package has no janitor.go: Table Storage entities carry no TTL/expiry concept (there is no message-visibility or blob-lease analogue), so there is nothing for a background sweep to do.
Index ¶
- Constants
- Variables
- func EvaluateFilter(node Node, entity EntityInfo) bool
- type ConfigProvider
- type EdmType
- type EntityInfo
- type EntityProperty
- type Handler
- func (h *Handler) ExtractOperation(c *echo.Context) string
- func (h *Handler) ExtractResource(c *echo.Context) string
- func (h *Handler) GetSupportedOperations() []string
- func (h *Handler) Handler() echo.HandlerFunc
- func (h *Handler) MatchPriority() int
- func (h *Handler) Name() string
- func (h *Handler) Reset()
- func (h *Handler) Restore(ctx context.Context, data []byte) error
- func (h *Handler) RouteMatcher() service.Matcher
- func (h *Handler) Shutdown(ctx context.Context)
- func (h *Handler) Snapshot(ctx context.Context) []byte
- func (h *Handler) StartWorker(ctx context.Context) error
- type InMemoryBackend
- type Node
- type Provider
- type Settings
- type StorageBackend
- type TableInfo
Constants ¶
const ( EdmString = odatatable.EdmString EdmInt32 = odatatable.EdmInt32 EdmInt64 = odatatable.EdmInt64 EdmDouble = odatatable.EdmDouble EdmBoolean = odatatable.EdmBoolean EdmDateTime = odatatable.EdmDateTime EdmGUID = odatatable.EdmGUID EdmBinary = odatatable.EdmBinary )
Supported EDM property types. EdmString is the default for a bare JSON string with no "@odata.type" annotation; EdmInt32 is the default for a bare, whole-number JSON number. See entity_ops.go's decodeProperty for the exact inference rules (mirroring azure-sdk-for-go/sdk/data/aztables's own EDMEntity.UnmarshalJSON, so unmodified SDK round trips match).
const DefaultPort = 10002
DefaultPort is Azure Table's fixed, protocol-conventional TCP port. This follows the same pattern as services/azureblob's DefaultPort (10000) and services/azurequeue's DefaultPort (10001): pick one default and try to bind exactly that, rather than drawing from cli.go's shared --port-range-start/--port-range-end PortAlloc pool. The default value itself (10002) is Azurite's own Table service port, so unmodified UseDevelopmentStorage=true-style SDK configuration works out of the box; see AZURE.md section 4 for the full rationale, including why this deliberately does NOT fall back into the shared PortAlloc pool if 10002 is taken (StartWorker fails fast instead -- see handler.go).
const IfMatchAny = odatatable.IfMatchAny
IfMatchAny is the wildcard If-Match value ("*") meaning "must currently exist, but match unconditionally on ETag" -- as opposed to an empty ifMatch (no header at all, meaning upsert semantics) or a specific ETag string (optimistic-concurrency match required).
Variables ¶
var ( ErrTableNotFound = odatatable.ErrTableNotFound ErrTableAlreadyExists = odatatable.ErrTableAlreadyExists ErrEntityNotFound = odatatable.ErrEntityNotFound ErrEntityAlreadyExists = odatatable.ErrEntityAlreadyExists ErrETagMismatch = odatatable.ErrETagMismatch // ErrInvalidEntityKey is returned when a request omits PartitionKey or // RowKey entirely. Empty-string keys are accepted (matching real Azure // Table Storage); only an absent key is rejected. See entity_ops.go. ErrInvalidEntityKey = odatatable.ErrInvalidEntityKey // ErrInvalidEntityProperty is returned when an entity property's JSON // value cannot be decoded under its (explicit or inferred) EDM type. ErrInvalidEntityProperty = odatatable.ErrInvalidEntityProperty // ErrFilterParse and ErrFilterTooDeep are returned by ParseFilter. A // parse error always surfaces as 400 InvalidInput, never a panic or 500 // -- see handler.go's queryEntities. ErrFilterParse = odatatable.ErrFilterParse ErrFilterTooDeep = odatatable.ErrFilterTooDeep // ErrSnapshotTableNull and ErrSnapshotEntityNull are returned by Restore // when a snapshot's "tables" map (or a table's "Entities" map) holds a // JSON null entry, which decodes to a nil pointer that would panic on // first dereference if stored as-is. See persistence.go. ErrSnapshotTableNull = odatatable.ErrSnapshotTableNull ErrSnapshotEntityNull = odatatable.ErrSnapshotEntityNull // ErrSnapshotTableNameMismatch is returned by Restore when a snapshot's // "tables" map key differs from that entry's storedTable.Name. Table // operations all key off the map, while ListTables reads Name -- a // mismatch would let those two views disagree about a table's identity. // See persistence.go. ErrSnapshotTableNameMismatch = odatatable.ErrSnapshotTableNameMismatch // ErrSnapshotEntityKeyMismatch is returned by Restore when a table's // "Entities" map key differs from that entry's own derived // (PartitionKey, RowKey) key. See persistence.go. ErrSnapshotEntityKeyMismatch = odatatable.ErrSnapshotEntityKeyMismatch )
Sentinel errors for Azure Table Storage operations. These are re-exported from pkgs/odatatable (the shared entity CRUD/$filter engine extracted from this package -- see AZURE.md section 9's M6 milestone and pkgs/odatatable's package doc comment) rather than declared locally, so existing callers/tests referencing azuretable.ErrTableNotFound and friends keep working unchanged: errors.Is comparisons still succeed since each var here is the exact same error value odatatable's engine returns.
var ErrNilAppContext = errors.New("azuretable: nil app context")
ErrNilAppContext is returned when Init is called with a nil AppContext.
Functions ¶
func EvaluateFilter ¶
func EvaluateFilter(node Node, entity EntityInfo) bool
EvaluateFilter reports whether entity satisfies the parsed $filter tree node. See pkgs/odatatable.EvaluateFilter (this package's re-exported engine -- see interfaces.go's package doc comment) for the full semantics.
Types ¶
type ConfigProvider ¶
type ConfigProvider interface {
GetAzureTableSettings() Settings
}
ConfigProvider is a private interface to extract AzureTable configuration from the abstract AppContext Config, mirroring services/azurequeue.ConfigProvider.
type EdmType ¶
type EdmType = odatatable.EdmType
EdmType identifies an entity property's OData EDM type.
type EntityInfo ¶
type EntityInfo = odatatable.EntityInfo
EntityInfo is a read-only snapshot of an entity.
type EntityProperty ¶
type EntityProperty = odatatable.EntityProperty
EntityProperty is a single typed entity property value.
type Handler ¶
type Handler struct {
Backend StorageBackend
// Endpoint is e.g. "http://127.0.0.1:10002" -- used to build
// odata.metadata/odata.id URLs in entity/table responses.
Endpoint string
// Port is the TCP port StartWorker binds. Set from Settings at Init time
// (see provider.go); defaults to DefaultPort. Like services/azurequeue,
// this is a single fixed, protocol-conventional port -- there is no
// fallback pool, so StartWorker fails fast if it's unavailable rather
// than silently binding a different port.
Port int
// contains filtered or unexported fields
}
Handler is the Echo HTTP handler for Azure Table Storage operations.
func NewHandler ¶
func NewHandler(backend StorageBackend) *Handler
NewHandler creates a new Azure Table Handler. Port defaults to DefaultPort; callers (typically provider.go) override it from Settings.
func (*Handler) ExtractOperation ¶
ExtractOperation extracts the Azure Table operation name from the request, for metrics labeling.
func (*Handler) ExtractResource ¶
ExtractResource extracts the table/entity resource identifier from the request path, for metrics labeling.
func (*Handler) GetSupportedOperations ¶
GetSupportedOperations returns the list of supported Azure Table operations.
func (*Handler) Handler ¶
func (h *Handler) Handler() echo.HandlerFunc
Handler returns the Echo handler function for Azure Table operations.
func (*Handler) MatchPriority ¶
MatchPriority returns the routing priority for the AzureTable handler. Irrelevant in practice since RouteMatcher never matches; 0 (lowest) is the safe default.
func (*Handler) Reset ¶
func (h *Handler) Reset()
Reset clears all in-memory state from the backend. It is used by the POST /_gopherstack/reset endpoint for CI pipelines and rapid local development.
func (*Handler) RouteMatcher ¶
RouteMatcher exists only to satisfy service.Registerable's interface contract: like services/azureblob and services/azurequeue, AzureTable deliberately never matches on the shared AWS single-port Router. It runs on its own dedicated listener started by StartWorker (see provider.go for the full rationale). Only RouteMatcher itself is inert, kept so *Handler satisfies service.Registerable.
func (*Handler) Shutdown ¶
Shutdown stops the dedicated Table listener. A graceful Shutdown error (e.g. its context expiring before active connections finish) is logged and followed by Close, which forcibly closes the listener and any remaining idle/active connections; any Close error is logged too rather than leaving the listener to leak silently.
func (*Handler) Snapshot ¶
Snapshot implements persistence.Persistable by delegating to the backend.
func (*Handler) StartWorker ¶
StartWorker binds the dedicated Table listener and starts serving on it. See provider.go's Provider doc comment for why AzureTable needs its own listener instead of registering into the shared AWS Router, and services/azurequeue's StartWorker for the synchronous-bind rationale this mirrors exactly.
type InMemoryBackend ¶
type InMemoryBackend = odatatable.InMemoryBackend
InMemoryBackend implements StorageBackend using an in-memory map. It is a re-exported alias onto pkgs/odatatable.InMemoryBackend (see interfaces.go's package doc comment): the engine itself -- including its Snapshot/Restore persistence methods (see persistence.go) -- now lives there so services/cosmosdb's Table API can construct its own independent instance of the exact same backend.
func NewInMemoryBackend ¶
func NewInMemoryBackend() *InMemoryBackend
NewInMemoryBackend creates a new empty InMemoryBackend.
type Node ¶
type Node = odatatable.Node
Node is a node in a parsed $filter expression tree. Re-exported from pkgs/odatatable (see interfaces.go's package doc comment): the $filter lexer/parser/evaluator itself now lives there.
func ParseFilter ¶
ParseFilter parses a complete $filter expression string into a Node tree. See pkgs/odatatable.ParseFilter for the full grammar and error contract.
type Provider ¶
type Provider struct{}
Provider implements service.Provider for the Azure Table Storage service.
Like services/azureblob and services/azurequeue, AzureTable does not register a RouteMatcher into the shared AWS single-port Router: Azure Table's path shape (/<account>/<resource>) has no service-identifying header the way AWS's X-Amz-Target does, and shares the same /<account>/<resource> shape as Azure Blob and Queue, so multiplexing it onto the shared port (or either of their own dedicated ports) risks exactly the collision the router avoids by construction for AWS services (see AZURE.md section 4). Instead the returned Handler implements service.BackgroundWorker and stands up its own dedicated *echo.Echo/*http.Server, listening on a fixed, protocol-conventional port (Azurite's own Table port, 10002). It is registered in cli.go's getMostRecentServiceProviders like every other provider; only its RouteMatcher (which always returns false) is inert.
Unlike services/azureblob and services/azurequeue, AzureTable has no janitor: Table Storage entities carry no TTL/expiry concept for a background sweep to enforce.
func (*Provider) Init ¶
func (p *Provider) Init(ctx *service.AppContext) (service.Registerable, error)
Init initializes the AzureTable service backend and handler. The configured port (Settings.Port, default DefaultPort) is only recorded here; the actual TCP bind happens synchronously in Handler.StartWorker, so a port-in-use failure is returned to the caller directly instead of being discovered later from a background goroutine.
type Settings ¶
type Settings struct {
// Port is the fixed TCP port for the dedicated Table listener. See
// handler.go's StartWorker for what happens when it's unavailable
// (fails fast; no fallback pool, matching services/azureblob and
// services/azurequeue).
Port int `` //nolint:lll // config struct tags are intentionally verbose
/* 178-byte string literal not displayed */
}
Settings holds service-level configuration for the Azure Table backend. Fields are picked up by the Kong CLI parser when this struct is embedded in the root CLI command (see cli.go's CLI.AzureTable field), mirroring services/azurequeue's Settings pattern.
func DefaultSettings ¶
func DefaultSettings() Settings
DefaultSettings returns the default Settings. Used when no ConfigProvider is available at init time (e.g. tests constructing a Provider directly).
type StorageBackend ¶
type StorageBackend = odatatable.StorageBackend
StorageBackend defines the interface for an Azure Table Storage backend. Re-exported from pkgs/odatatable.StorageBackend (see this file's package doc comment) so existing callers referencing azuretable.StorageBackend keep compiling unchanged.
The ifMatch parameter on ReplaceEntity/MergeEntity/DeleteEntity threads through the three If-Match states the wire protocol distinguishes:
- "" (no If-Match header): upsert semantics for Replace/MergeEntity (create if absent, otherwise mutate unconditionally); DeleteEntity's caller (handler.go) never passes "" -- an absent If-Match on Delete is rejected at the handler layer before the backend is ever called.
- IfMatchAny ("*"): the entity must exist, but any current ETag matches.
- any other string: the entity must exist AND its current ETag must equal this value, else ErrETagMismatch.
type TableInfo ¶
type TableInfo = odatatable.TableInfo
TableInfo is a read-only snapshot of a table's metadata.