diff --git a/database/models.go b/database/models.go index ebbeb658..7e8fd0a9 100644 --- a/database/models.go +++ b/database/models.go @@ -17,6 +17,7 @@ package database import ( "database/sql/driver" "encoding/json" + "fmt" "time" "github.com/coreos/clair/pkg/pagination" @@ -123,10 +124,13 @@ func (l *Layer) GetFeatures() []Feature { } func (l *Layer) GetNamespaces() []Namespace { - namespaces := make([]Namespace, 0, len(l.Namespaces)) + namespaces := make([]Namespace, 0, len(l.Namespaces)+len(l.Features)) for _, ns := range l.Namespaces { namespaces = append(namespaces, ns.Namespace) } + for _, f := range l.Features { + namespaces = append(namespaces, f.Feature.PotentialNamespace) + } return namespaces } @@ -195,6 +199,10 @@ type NamespacedFeature struct { Namespace Namespace `json:"namespace"` } +func (nf *NamespacedFeature) Key() string { + return fmt.Sprintf("%s-%s-%s-%s-%s-%s", nf.Name, nf.Version, nf.VersionFormat, nf.Type, nf.Namespace.Name, nf.Namespace.VersionFormat) +} + func NewNamespacedFeature(namespace *Namespace, feature *Feature) *NamespacedFeature { // TODO: namespaced feature should use pointer values return &NamespacedFeature{*feature, *namespace} diff --git a/database/pgsql/feature.go b/database/pgsql/feature.go index 66b47c50..58f12dbd 100644 --- a/database/pgsql/feature.go +++ b/database/pgsql/feature.go @@ -17,6 +17,7 @@ package pgsql import ( "database/sql" "sort" + "strconv" "github.com/lib/pq" log "github.com/sirupsen/logrus" @@ -311,7 +312,7 @@ func (tx *pgSession) findNamespacedFeatureIDs(nfs []database.NamespacedFeature) return nil, nil } - nfsMap := map[database.NamespacedFeature]int64{} + nfsMap := map[string]int64{} keys := make([]interface{}, 0, len(nfs)*5) for _, nf := range nfs { keys = append(keys, nf.Name, nf.Version, nf.VersionFormat, nf.Type, nf.Namespace.Name) @@ -334,12 +335,12 @@ func (tx *pgSession) findNamespacedFeatureIDs(nfs []database.NamespacedFeature) if err != nil { return nil, handleError("searchNamespacedFeature", err) } - nfsMap[nf] = id + nfsMap[nf.Key()] = id } ids := make([]sql.NullInt64, len(nfs)) for i, nf := range nfs { - if id, ok := nfsMap[nf]; ok { + if id, ok := nfsMap[nf.Key()]; ok { ids[i] = sql.NullInt64{id, true} } else { ids[i] = sql.NullInt64{} @@ -359,13 +360,14 @@ func (tx *pgSession) findFeatureIDs(fs []database.Feature) ([]sql.NullInt64, err return nil, err } - fMap := map[database.Feature]sql.NullInt64{} + fMap := map[string]sql.NullInt64{} keys := make([]interface{}, 0, len(fs)*4) for _, f := range fs { typeID := types.byName[f.Type] keys = append(keys, f.Name, f.Version, f.VersionFormat, typeID) - fMap[f] = sql.NullInt64{} + mapKey := f.Name + f.Version + f.VersionFormat + strconv.Itoa(typeID) + fMap[mapKey] = sql.NullInt64{} } rows, err := tx.Query(querySearchFeatureID(len(fs)), keys...) @@ -386,12 +388,15 @@ func (tx *pgSession) findFeatureIDs(fs []database.Feature) ([]sql.NullInt64, err } f.Type = types.byID[typeID] - fMap[f] = id + mapKey := f.Name + f.Version + f.VersionFormat + strconv.Itoa(typeID) + fMap[mapKey] = id } ids := make([]sql.NullInt64, len(fs)) for i, f := range fs { - ids[i] = fMap[f] + typeID := types.byName[f.Type] + mapKey := f.Name + f.Version + f.VersionFormat + strconv.Itoa(typeID) + ids[i] = fMap[mapKey] } return ids, nil diff --git a/database/pgsql/feature_test.go b/database/pgsql/feature_test.go index 4f546c91..7b47b37f 100644 --- a/database/pgsql/feature_test.go +++ b/database/pgsql/feature_test.go @@ -182,7 +182,7 @@ func TestFindNamespacedFeatureIDs(t *testing.T) { expectedIDs = append(expectedIDs, 1) namespace := realNamespaces[1] - features = append(features, *database.NewNamespacedFeature(&namespace, database.NewBinaryPackage("not-found", "1.0", "dpkg"))) // test not found feature + features = append(features, *database.NewNamespacedFeature(&namespace, database.NewBinaryPackage("not-found", "1.0", "dpkg", database.Namespace{}))) // test not found feature ids, err := tx.findNamespacedFeatureIDs(features) require.Nil(t, err) diff --git a/database/pgsql/layer.go b/database/pgsql/layer.go index a071eb4b..9561c7a1 100644 --- a/database/pgsql/layer.go +++ b/database/pgsql/layer.go @@ -37,10 +37,11 @@ const ( SELECT id FROM layer WHERE hash = $1` findLayerFeatures = ` - SELECT f.name, f.version, f.version_format, t.name, lf.detector_id - FROM layer_feature AS lf, feature AS f, feature_type AS t + SELECT f.name, f.version, f.version_format, t.name, lf.detector_id, ns.name, ns.version_format + FROM layer_feature AS lf, feature AS f, feature_type AS t, namespace AS ns WHERE lf.feature_id = f.id AND t.id = f.type + AND lf.namespace_id = ns.id AND lf.layer_id = $1` findLayerNamespaces = ` @@ -61,9 +62,10 @@ type dbLayerNamespace struct { // dbLayerFeature represents the layer_feature table type dbLayerFeature struct { - layerID int64 - featureID int64 - detectorID int64 + layerID int64 + featureID int64 + detectorID int64 + namespaceID int64 } func (tx *pgSession) FindLayer(hash string) (database.Layer, bool, error) { @@ -199,10 +201,16 @@ func (tx *pgSession) persistAllLayerFeatures(layerID int64, features []database. if err != nil { return err } - + var namespaces []database.Namespace + for _, feature := range features { + namespaces = append(namespaces, feature.PotentialNamespace) + } + nameSpaceIDs, _ := tx.findNamespaceIDs(namespaces) + featureNamespaceMap := map[database.Namespace]sql.NullInt64{} rawFeatures := make([]database.Feature, 0, len(features)) - for _, f := range features { + for i, f := range features { rawFeatures = append(rawFeatures, f.Feature) + featureNamespaceMap[f.PotentialNamespace] = nameSpaceIDs[i] } featureIDs, err := tx.findFeatureIDs(rawFeatures) @@ -213,12 +221,16 @@ func (tx *pgSession) persistAllLayerFeatures(layerID int64, features []database. dbFeatures := make([]dbLayerFeature, 0, len(features)) for i, f := range features { detectorID := detectorMap.byValue[f.By] - featureID := featureIDs[i].Int64 if !featureIDs[i].Valid { return database.ErrMissingEntities } + featureID := featureIDs[i].Int64 + if !featureNamespaceMap[f.PotentialNamespace].Valid { + return database.ErrMissingEntities + } + namespaceID := featureNamespaceMap[f.PotentialNamespace].Int64 - dbFeatures = append(dbFeatures, dbLayerFeature{layerID, featureID, detectorID}) + dbFeatures = append(dbFeatures, dbLayerFeature{layerID, featureID, detectorID, namespaceID}) } if err := tx.persistLayerFeatures(dbFeatures); err != nil { @@ -236,9 +248,9 @@ func (tx *pgSession) persistLayerFeatures(features []dbLayerFeature) error { sort.Slice(features, func(i, j int) bool { return features[i].featureID < features[j].featureID }) - keys := make([]interface{}, 0, len(features)*3) + keys := make([]interface{}, 0, len(features)*4) for _, f := range features { - keys = append(keys, f.layerID, f.featureID, f.detectorID) + keys = append(keys, f.layerID, f.featureID, f.detectorID, f.namespaceID) } _, err := tx.Exec(queryPersistLayerFeature(len(features)), keys...) @@ -308,7 +320,7 @@ func (tx *pgSession) findLayerFeatures(layerID int64, detectors detectorMap) ([] detectorID int64 feature database.LayerFeature ) - if err := rows.Scan(&feature.Name, &feature.Version, &feature.VersionFormat, &feature.Type, &detectorID); err != nil { + if err := rows.Scan(&feature.Name, &feature.Version, &feature.VersionFormat, &feature.Type, &detectorID, &feature.PotentialNamespace.Name, &feature.PotentialNamespace.VersionFormat); err != nil { return nil, handleError("findLayerFeatures", err) } diff --git a/database/pgsql/migrations/00001_initial_schema.go b/database/pgsql/migrations/00001_initial_schema.go index b1f3bd76..85b2b49c 100644 --- a/database/pgsql/migrations/00001_initial_schema.go +++ b/database/pgsql/migrations/00001_initial_schema.go @@ -89,6 +89,7 @@ var ( layer_id INT REFERENCES layer ON DELETE CASCADE, feature_id INT REFERENCES feature ON DELETE CASCADE, detector_id INT REFERENCES detector ON DELETE CASCADE, + namespace_id INT REFERENCES namespace ON DELETE CASCADE, UNIQUE (layer_id, feature_id));`, `CREATE INDEX ON layer_feature(layer_id);`, diff --git a/database/pgsql/queries.go b/database/pgsql/queries.go index 5cd5c3c9..111dc30e 100644 --- a/database/pgsql/queries.go +++ b/database/pgsql/queries.go @@ -124,7 +124,8 @@ func queryPersistLayerFeature(count int) string { "layer_feature_layer_id_feature_id_key", "layer_id", "feature_id", - "detector_id") + "detector_id", + "namespace_id") } func queryPersistNamespace(count int) string {