Skip to content

Commit

Permalink
SQL schema feedback fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
reinkrul committed Nov 20, 2023
1 parent 984f8aa commit fe929e3
Show file tree
Hide file tree
Showing 10 changed files with 74 additions and 98 deletions.
29 changes: 0 additions & 29 deletions discoveryservice/api/v1/api.go

This file was deleted.

2 changes: 0 additions & 2 deletions discoveryservice/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ type DefinitionsConfig struct {
type ServerConfig struct {
// DefinitionIDs specifies which use case lists the server serves.
DefinitionIDs []string `koanf:"definition_ids"`
// Directory is the directory where the server stores the lists.
Directory string `koanf:"directory"`
}

// DefaultConfig returns the default configuration.
Expand Down
5 changes: 3 additions & 2 deletions discoveryservice/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ func init() {
panic(err)
}
const schemaURL = "http://nuts.nl/schemas/discovery-service-v0.json"
if err := v2.Compiler.AddResource(schemaURL, bytes.NewReader(serviceDefinitionSchemaData)); err != nil {
compiler := v2.Compiler()
if err := compiler.AddResource(schemaURL, bytes.NewReader(serviceDefinitionSchemaData)); err != nil {
panic(err)
}
definitionJsonSchema = v2.Compiler.MustCompile(schemaURL)
definitionJsonSchema = compiler.MustCompile(schemaURL)
}

// Definition holds the definition of a service.
Expand Down
2 changes: 1 addition & 1 deletion discoveryservice/log/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"github.com/sirupsen/logrus"
)

var _logger = logrus.StandardLogger().WithField(core.LogFieldModule, "DiscoveryService")
var _logger = logrus.StandardLogger().WithField(core.LogFieldModule, "Discovery")

// Logger returns a logger with the module field set
func Logger() *logrus.Entry {
Expand Down
2 changes: 1 addition & 1 deletion discoveryservice/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (
"time"
)

const ModuleName = "DiscoveryService"
const ModuleName = "Discovery"

var ErrServerModeDisabled = errors.New("node is not a discovery server for this service")

Expand Down
2 changes: 1 addition & 1 deletion discoveryservice/module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
const serviceID = "urn:nuts.nl:usecase:eOverdrachtDev2023"

func TestModule_Name(t *testing.T) {
assert.Equal(t, "DiscoveryService", (&Module{}).Name())
assert.Equal(t, "Discovery", (&Module{}).Name())
}

func TestModule_Shutdown(t *testing.T) {
Expand Down
46 changes: 23 additions & 23 deletions discoveryservice/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,20 @@ var ErrServiceNotFound = errors.New("discovery service not found")
var ErrPresentationAlreadyExists = errors.New("presentation already exists")

type serviceRecord struct {
ID string `gorm:"primaryKey"`
Timestamp uint64
ID string `gorm:"primaryKey"`
LamportTimestamp uint64
}

func (s serviceRecord) TableName() string {
return "discoveryservices"
return "discovery_service"
}

var _ schema.Tabler = (*presentationRecord)(nil)

type presentationRecord struct {
ID string `gorm:"primaryKey"`
ServiceID string
Timestamp uint64
LamportTimestamp uint64
CredentialSubjectID string
PresentationID string
PresentationRaw string
Expand All @@ -61,14 +61,14 @@ type presentationRecord struct {
}

func (s presentationRecord) TableName() string {
return "discoveryservice_presentations"
return "discovery_presentation"
}

// credentialRecord is a Verifiable Credential, part of a presentation (entry) on a use case list.
type credentialRecord struct {
// ID is the unique identifier of the entry.
ID string `gorm:"primaryKey"`
// PresentationID corresponds to the discoveryservice_presentations record ID (not VerifiablePresentation.ID) this credentialRecord belongs to.
// PresentationID corresponds to the discovery_presentation record ID (not VerifiablePresentation.ID) this credentialRecord belongs to.
PresentationID string
// CredentialID contains the 'id' property of the Verifiable Credential.
CredentialID string
Expand All @@ -78,18 +78,18 @@ type credentialRecord struct {
CredentialSubjectID string
// CredentialType contains the 'type' property of the Verifiable Credential (not being 'VerifiableCredential').
CredentialType *string
Properties []credentialPropertyRecord `gorm:"foreignKey:ID;references:ID"`
Properties []credentialPropertyRecord `gorm:"foreignKey:CredentialID;references:ID"`
}

// TableName returns the table name for this DTO.
func (p credentialRecord) TableName() string {
return "discoveryservice_credentials"
return "discovery_credential"
}

// credentialPropertyRecord is a property of a Verifiable Credential in a Verifiable Presentation in a discovery service.
type credentialPropertyRecord struct {
// ID refers to the entry record in discoveryservice_credentials
ID string `gorm:"primaryKey"`
// CredentialID refers to the entry record in discovery_credential
CredentialID string `gorm:"primaryKey"`
// Key is JSON path of the property.
Key string `gorm:"primaryKey"`
// Value is the value of the property.
Expand All @@ -98,7 +98,7 @@ type credentialPropertyRecord struct {

// TableName returns the table name for this DTO.
func (l credentialPropertyRecord) TableName() string {
return "discoveryservice_credential_props"
return "discovery_credential_prop"
}

type sqlStore struct {
Expand Down Expand Up @@ -183,7 +183,7 @@ func createPresentationRecord(serviceID string, timestamp Timestamp, presentatio
newPresentation := presentationRecord{
ID: uuid.NewString(),
ServiceID: serviceID,
Timestamp: uint64(timestamp),
LamportTimestamp: uint64(timestamp),
CredentialSubjectID: credentialSubjectID.String(),
PresentationID: presentation.ID.String(),
PresentationRaw: presentation.Raw(),
Expand Down Expand Up @@ -219,9 +219,9 @@ func createPresentationRecord(serviceID string, timestamp Timestamp, presentatio
continue
}
newCredential.Properties = append(newCredential.Properties, credentialPropertyRecord{
ID: newCredential.ID,
Key: key,
Value: values[i],
CredentialID: newCredential.ID,
Key: key,
Value: values[i],
})
}
newPresentation.Credentials = append(newPresentation.Credentials, newCredential)
Expand All @@ -231,7 +231,7 @@ func createPresentationRecord(serviceID string, timestamp Timestamp, presentatio

func (s *sqlStore) get(serviceID string, startAt Timestamp) ([]vc.VerifiablePresentation, *Timestamp, error) {
var rows []presentationRecord
err := s.db.Order("timestamp ASC").Find(&rows, "service_id = ? AND timestamp > ?", serviceID, int(startAt)).Error
err := s.db.Order("lamport_timestamp ASC").Find(&rows, "service_id = ? AND lamport_timestamp > ?", serviceID, int(startAt)).Error
if err != nil {
return nil, nil, fmt.Errorf("query service '%s': %w", serviceID, err)
}
Expand All @@ -243,7 +243,7 @@ func (s *sqlStore) get(serviceID string, startAt Timestamp) ([]vc.VerifiablePres
return nil, nil, fmt.Errorf("parse presentation '%s' of service '%s': %w", row.PresentationID, serviceID, err)
}
presentations = append(presentations, *presentation)
timestamp = Timestamp(row.Timestamp)
timestamp = Timestamp(row.LamportTimestamp)
}
return presentations, &timestamp, nil
}
Expand All @@ -258,7 +258,7 @@ func (s *sqlStore) search(serviceID string, query map[string]string) ([]vc.Verif

stmt := s.db.Model(&presentationRecord{}).
Where("service_id = ?", serviceID).
Joins("inner join discoveryservice_credentials cred ON cred.presentation_id = discoveryservice_presentations.id")
Joins("inner join discovery_credential cred ON cred.presentation_id = discovery_presentation.id")
numProps := 0
for jsonPath, value := range query {
if value == "*" {
Expand All @@ -281,7 +281,7 @@ func (s *sqlStore) search(serviceID string, query map[string]string) ([]vc.Verif
// Multiple (inner) joins to filter on a dynamic number of properties to filter on is not pretty, but it works
alias := "p" + strconv.Itoa(numProps)
numProps++
stmt = stmt.Joins("inner join discoveryservice_credential_props "+alias+" ON "+alias+".id = cred.id AND "+alias+".key = ? AND "+alias+".value "+eq+" ?", jsonPath, value)
stmt = stmt.Joins("inner join discovery_credential_prop "+alias+" ON "+alias+".credential_id = cred.id AND "+alias+".key = ? AND "+alias+".value "+eq+" ?", jsonPath, value)
}
}

Expand All @@ -305,7 +305,7 @@ func (s *sqlStore) search(serviceID string, query map[string]string) ([]vc.Verif

func (s *sqlStore) updateTimestamp(tx *gorm.DB, serviceID string, newTimestamp *Timestamp) (Timestamp, error) {
var result serviceRecord
// Lock (SELECT FOR UPDATE) discoveryservices row to prevent concurrent updates to the same list, which could mess up the lamport timestamp.
// Lock (SELECT FOR UPDATE) discoveryservice row to prevent concurrent updates to the same list, which could mess up the lamport timestamp.
// But, it is not supported by SQLite. SQLite defaults to table-level write-locks upon the first write action in the transaction.
//
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
Expand All @@ -317,14 +317,14 @@ func (s *sqlStore) updateTimestamp(tx *gorm.DB, serviceID string, newTimestamp *
result.ID = serviceID
if newTimestamp == nil {
// Increment timestamp
result.Timestamp++
result.LamportTimestamp++
} else {
result.Timestamp = uint64(*newTimestamp)
result.LamportTimestamp = uint64(*newTimestamp)
}
if err := tx.Save(&result).Error; err != nil {
return 0, err
}
return Timestamp(result.Timestamp), nil
return Timestamp(result.LamportTimestamp), nil
}

func (s *sqlStore) exists(serviceID string, credentialSubjectID string, presentationID string) (bool, error) {
Expand Down
2 changes: 1 addition & 1 deletion discoveryservice/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ func resetStore(t *testing.T, db *gorm.DB) {
underlyingDB, err := db.DB()
require.NoError(t, err)
// related tables are emptied due to on-delete-cascade clause
_, err = underlyingDB.Exec("DELETE FROM discoveryservices")
_, err = underlyingDB.Exec("DELETE FROM discovery_service")
require.NoError(t, err)
}

Expand Down
60 changes: 30 additions & 30 deletions storage/sql_migrations/2_discoveryservice.up.sql
Original file line number Diff line number Diff line change
@@ -1,49 +1,49 @@
-- discoveryservices contains the known discovery services and the highest timestamp
create table discoveryservices
-- discovery contains the known discovery services and the highest timestamp
create table discovery_service
(
id text not null primary key,
timestamp integer not null
id varchar(36) not null primary key,
lamport_timestamp integer not null
);

-- discoveryservice_presentations contains the presentations of the discovery services
create table discoveryservice_presentations
-- discovery_presentation contains the presentations of the discovery services
create table discovery_presentation
(
id text not null primary key,
service_id text not null,
timestamp integer not null,
credential_subject_id text not null,
presentation_id text not null,
presentation_raw text not null,
presentation_expiration integer not null,
id varchar(36) not null primary key,
service_id varchar(36) not null,
lamport_timestamp integer not null,
credential_subject_id varchar not null,
presentation_id varchar not null,
presentation_raw varchar not null,
presentation_expiration integer not null,
unique (service_id, credential_subject_id),
constraint fk_discovery_presentation_service_id foreign key (service_id) references discoveryservices (id) on delete cascade
constraint fk_discovery_presentation_service_id foreign key (service_id) references discovery_service (id) on delete cascade
);

-- discoveryservice_credentials is a credential in a presentation of the discovery service.
-- discovery_credential is a credential in a presentation of the discovery service.
-- We could do without the table, but having it allows to have a normalized index for credential properties that appear on every credential.
-- Then we don't need rows in the properties table for them (having a column for those is faster than having a row in the properties table which needs to be joined).
create table discoveryservice_credentials
create table discovery_credential
(
id text not null primary key,
presentation_id text not null,
credential_id text not null,
credential_issuer text not null,
credential_subject_id text not null,
id varchar(36) not null primary key,
presentation_id varchar(36) not null,
credential_id varchar not null,
credential_issuer varchar not null,
credential_subject_id varchar not null,
-- for now, credentials with at most 2 types are supported.
-- The type stored in the type column will be the 'other' type, not being 'VerifiableCredential'.
-- When credentials with 3 or more types appear, we could have to use a separate table for the types.
credential_type text,
constraint fk_discoveryservice_credential_presentation foreign key (presentation_id) references discoveryservice_presentations (id) on delete cascade
credential_type varchar,
constraint fk_discoveryservice_credential_presentation foreign key (presentation_id) references discovery_presentation (id) on delete cascade
);

-- discoveryservice_credential_props contains the credentialSubject properties of a credential in a presentation of the discovery service.
-- discovery_credential_prop contains the credentialSubject properties of a credential in a presentation of the discovery service.
-- It is used by clients to search for presentations.
create table discoveryservice_credential_props
create table discovery_credential_prop
(
id text not null,
key text not null,
value text,
PRIMARY KEY (id, key),
credential_id varchar(36) not null,
key varchar not null,
value varchar,
PRIMARY KEY (credential_id, key),
-- cascading delete: if the presentation gets deleted, the properties get deleted as well
constraint fk_discoveryservice_credential_id foreign key (id) references discoveryservice_credentials (id) on delete cascade
constraint fk_discoveryservice_credential_id foreign key (credential_id) references discovery_credential (id) on delete cascade
);
22 changes: 14 additions & 8 deletions vcr/pe/schema/v2/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,27 @@ var PresentationDefinition *jsonschema.Schema
// PresentationSubmission is the JSON schema for a presentation submission.
var PresentationSubmission *jsonschema.Schema

// Compiler is the JSON schema compiler.
var Compiler = jsonschema.NewCompiler()
// Compiler returns a JSON schema compiler with the Presentation Exchange schemas loaded.
func Compiler() *jsonschema.Compiler {
compiler := jsonschema.NewCompiler()
compiler.Draft = jsonschema.Draft7
if err := loadSchemas(schemaFiles, compiler); err != nil {
panic(err)
}
PresentationDefinition = compiler.MustCompile(presentationDefinition)
PresentationSubmission = compiler.MustCompile(presentationSubmission)
return compiler
}

func init() {
// By default, it loads from filesystem, but that sounds unsafe.
// Since register our schemas, we don't need to allow loading resources.
loader.Load = func(url string) (io.ReadCloser, error) {
return nil, fmt.Errorf("refusing to load unknown schema: %s", url)
}
Compiler.Draft = jsonschema.Draft7
if err := loadSchemas(schemaFiles, Compiler); err != nil {
panic(err)
}
PresentationDefinition = Compiler.MustCompile(presentationDefinition)
PresentationSubmission = Compiler.MustCompile(presentationSubmission)
compiler := Compiler()
PresentationDefinition = compiler.MustCompile(presentationDefinition)
PresentationSubmission = compiler.MustCompile(presentationSubmission)
}

func loadSchemas(reader fs.ReadFileFS, compiler *jsonschema.Compiler) error {
Expand Down

0 comments on commit fe929e3

Please sign in to comment.