diff --git a/database/pgsql/ancestry.go b/database/pgsql/ancestry.go deleted file mode 100644 index 40a49e86..00000000 --- a/database/pgsql/ancestry.go +++ /dev/null @@ -1,375 +0,0 @@ -// Copyright 2019 clair authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pgsql - -import ( - "database/sql" - "errors" - - log "github.com/sirupsen/logrus" - - "github.com/coreos/clair/database" - "github.com/coreos/clair/pkg/commonerr" -) - -const ( - insertAncestry = ` - INSERT INTO ancestry (name) VALUES ($1) RETURNING id` - - findAncestryLayerHashes = ` - SELECT layer.hash, ancestry_layer.ancestry_index - FROM layer, ancestry_layer - WHERE ancestry_layer.ancestry_id = $1 - AND ancestry_layer.layer_id = layer.id - ORDER BY ancestry_layer.ancestry_index ASC` - - findAncestryFeatures = ` - SELECT namespace.name, namespace.version_format, feature.name, - feature.version, feature.version_format, feature_type.name, ancestry_layer.ancestry_index, - ancestry_feature.feature_detector_id, ancestry_feature.namespace_detector_id - FROM namespace, feature, feature_type, namespaced_feature, ancestry_layer, ancestry_feature - WHERE ancestry_layer.ancestry_id = $1 - AND feature_type.id = feature.type - AND ancestry_feature.ancestry_layer_id = ancestry_layer.id - AND ancestry_feature.namespaced_feature_id = namespaced_feature.id - AND namespaced_feature.feature_id = feature.id - AND namespaced_feature.namespace_id = namespace.id` - - findAncestryID = `SELECT id FROM ancestry WHERE name = $1` - removeAncestry = `DELETE FROM ancestry WHERE name = $1` - insertAncestryLayers = ` - INSERT INTO ancestry_layer (ancestry_id, ancestry_index, layer_id) VALUES ($1, $2, $3) - RETURNING id` - insertAncestryFeatures = ` - INSERT INTO ancestry_feature - (ancestry_layer_id, namespaced_feature_id, feature_detector_id, namespace_detector_id) VALUES - ($1, $2, $3, $4)` -) - -func (tx *pgSession) FindAncestry(name string) (database.Ancestry, bool, error) { - var ( - ancestry = database.Ancestry{Name: name} - err error - ) - - id, ok, err := tx.findAncestryID(name) - if !ok || err != nil { - return ancestry, ok, err - } - - if ancestry.By, err = tx.findAncestryDetectors(id); err != nil { - return ancestry, false, err - } - - if ancestry.Layers, err = tx.findAncestryLayers(id); err != nil { - return ancestry, false, err - } - - return ancestry, true, nil -} - -func (tx *pgSession) UpsertAncestry(ancestry database.Ancestry) error { - if !ancestry.Valid() { - return database.ErrInvalidParameters - } - - if err := tx.removeAncestry(ancestry.Name); err != nil { - return err - } - - id, err := tx.insertAncestry(ancestry.Name) - if err != nil { - return err - } - - detectorIDs, err := tx.findDetectorIDs(ancestry.By) - if err != nil { - return err - } - - // insert ancestry metadata - if err := tx.insertAncestryDetectors(id, detectorIDs); err != nil { - return err - } - - layers := make([]string, 0, len(ancestry.Layers)) - for _, layer := range ancestry.Layers { - layers = append(layers, layer.Hash) - } - - layerIDs, ok, err := tx.findLayerIDs(layers) - if err != nil { - return err - } - - if !ok { - log.Error("layer cannot be found, this indicates that the internal logic of calling UpsertAncestry is wrong or the database is corrupted.") - return database.ErrMissingEntities - } - - ancestryLayerIDs, err := tx.insertAncestryLayers(id, layerIDs) - if err != nil { - return err - } - - for i, id := range ancestryLayerIDs { - if err := tx.insertAncestryFeatures(id, ancestry.Layers[i]); err != nil { - return err - } - } - - return nil -} - -func (tx *pgSession) insertAncestry(name string) (int64, error) { - var id int64 - err := tx.QueryRow(insertAncestry, name).Scan(&id) - if err != nil { - if isErrUniqueViolation(err) { - return 0, handleError("insertAncestry", errors.New("other Go-routine is processing this ancestry (skip)")) - } - - return 0, handleError("insertAncestry", err) - } - - return id, nil -} - -func (tx *pgSession) findAncestryID(name string) (int64, bool, error) { - var id sql.NullInt64 - if err := tx.QueryRow(findAncestryID, name).Scan(&id); err != nil { - if err == sql.ErrNoRows { - return 0, false, nil - } - - return 0, false, handleError("findAncestryID", err) - } - - return id.Int64, true, nil -} - -func (tx *pgSession) removeAncestry(name string) error { - result, err := tx.Exec(removeAncestry, name) - if err != nil { - return handleError("removeAncestry", err) - } - - affected, err := result.RowsAffected() - if err != nil { - return handleError("removeAncestry", err) - } - - if affected != 0 { - log.WithField("ancestry", name).Debug("removed ancestry") - } - - return nil -} - -func (tx *pgSession) findAncestryLayers(id int64) ([]database.AncestryLayer, error) { - detectors, err := tx.findAllDetectors() - if err != nil { - return nil, err - } - - layerMap, err := tx.findAncestryLayerHashes(id) - if err != nil { - return nil, err - } - - featureMap, err := tx.findAncestryFeatures(id, detectors) - if err != nil { - return nil, err - } - - layers := make([]database.AncestryLayer, len(layerMap)) - for index, layer := range layerMap { - // index MUST match the ancestry layer slice index. - if layers[index].Hash == "" && len(layers[index].Features) == 0 { - layers[index] = database.AncestryLayer{ - Hash: layer, - Features: featureMap[index], - } - } else { - log.WithFields(log.Fields{ - "ancestry ID": id, - "duplicated ancestry index": index, - }).WithError(database.ErrInconsistent).Error("ancestry layers with same ancestry_index is not allowed") - return nil, database.ErrInconsistent - } - } - - return layers, nil -} - -func (tx *pgSession) findAncestryLayerHashes(ancestryID int64) (map[int64]string, error) { - // retrieve layer indexes and hashes - rows, err := tx.Query(findAncestryLayerHashes, ancestryID) - if err != nil { - return nil, handleError("findAncestryLayerHashes", err) - } - - layerHashes := map[int64]string{} - for rows.Next() { - var ( - hash string - index int64 - ) - - if err = rows.Scan(&hash, &index); err != nil { - return nil, handleError("findAncestryLayerHashes", err) - } - - if _, ok := layerHashes[index]; ok { - // one ancestry index should correspond to only one layer - return nil, database.ErrInconsistent - } - - layerHashes[index] = hash - } - - return layerHashes, nil -} - -func (tx *pgSession) findAncestryFeatures(ancestryID int64, detectors detectorMap) (map[int64][]database.AncestryFeature, error) { - // ancestry_index -> ancestry features - featureMap := make(map[int64][]database.AncestryFeature) - // retrieve ancestry layer's namespaced features - rows, err := tx.Query(findAncestryFeatures, ancestryID) - if err != nil { - return nil, handleError("findAncestryFeatures", err) - } - - defer rows.Close() - - for rows.Next() { - var ( - featureDetectorID int64 - namespaceDetectorID int64 - feature database.NamespacedFeature - // index is used to determine which layer the feature belongs to. - index sql.NullInt64 - ) - - if err := rows.Scan( - &feature.Namespace.Name, - &feature.Namespace.VersionFormat, - &feature.Feature.Name, - &feature.Feature.Version, - &feature.Feature.VersionFormat, - &feature.Feature.Type, - &index, - &featureDetectorID, - &namespaceDetectorID, - ); err != nil { - return nil, handleError("findAncestryFeatures", err) - } - - if feature.Feature.VersionFormat != feature.Namespace.VersionFormat { - // Feature must have the same version format as the associated - // namespace version format. - return nil, database.ErrInconsistent - } - - fDetector, ok := detectors.byID[featureDetectorID] - if !ok { - return nil, database.ErrInconsistent - } - - nsDetector, ok := detectors.byID[namespaceDetectorID] - if !ok { - return nil, database.ErrInconsistent - } - - featureMap[index.Int64] = append(featureMap[index.Int64], database.AncestryFeature{ - NamespacedFeature: feature, - FeatureBy: fDetector, - NamespaceBy: nsDetector, - }) - } - - return featureMap, nil -} - -// insertAncestryLayers inserts the ancestry layers along with its content into -// the database. The layers are 0 based indexed in the original order. -func (tx *pgSession) insertAncestryLayers(ancestryID int64, layers []int64) ([]int64, error) { - stmt, err := tx.Prepare(insertAncestryLayers) - if err != nil { - return nil, handleError("insertAncestryLayers", err) - } - - ancestryLayerIDs := []int64{} - for index, layerID := range layers { - var ancestryLayerID sql.NullInt64 - if err := stmt.QueryRow(ancestryID, index, layerID).Scan(&ancestryLayerID); err != nil { - return nil, handleError("insertAncestryLayers", commonerr.CombineErrors(err, stmt.Close())) - } - - if !ancestryLayerID.Valid { - return nil, database.ErrInconsistent - } - - ancestryLayerIDs = append(ancestryLayerIDs, ancestryLayerID.Int64) - } - - if err := stmt.Close(); err != nil { - return nil, handleError("insertAncestryLayers", err) - } - - return ancestryLayerIDs, nil -} - -func (tx *pgSession) insertAncestryFeatures(ancestryLayerID int64, layer database.AncestryLayer) error { - detectors, err := tx.findAllDetectors() - if err != nil { - return err - } - - nsFeatureIDs, err := tx.findNamespacedFeatureIDs(layer.GetFeatures()) - if err != nil { - return err - } - - // find the detectors for each feature - stmt, err := tx.Prepare(insertAncestryFeatures) - if err != nil { - return handleError("insertAncestryFeatures", err) - } - - defer stmt.Close() - - for index, id := range nsFeatureIDs { - if !id.Valid { - return database.ErrMissingEntities - } - - namespaceDetectorID, ok := detectors.byValue[layer.Features[index].NamespaceBy] - if !ok { - return database.ErrMissingEntities - } - - featureDetectorID, ok := detectors.byValue[layer.Features[index].FeatureBy] - if !ok { - return database.ErrMissingEntities - } - - if _, err := stmt.Exec(ancestryLayerID, id, featureDetectorID, namespaceDetectorID); err != nil { - return handleError("insertAncestryFeatures", commonerr.CombineErrors(err, stmt.Close())) - } - } - - return nil -} diff --git a/database/pgsql/ancestry/ancestry.go b/database/pgsql/ancestry/ancestry.go new file mode 100644 index 00000000..74fe0fd6 --- /dev/null +++ b/database/pgsql/ancestry/ancestry.go @@ -0,0 +1,160 @@ +// Copyright 2019 clair authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ancestry + +import ( + "database/sql" + "errors" + + log "github.com/sirupsen/logrus" + + "github.com/coreos/clair/database" + "github.com/coreos/clair/database/pgsql/detector" + "github.com/coreos/clair/database/pgsql/layer" + "github.com/coreos/clair/database/pgsql/util" +) + +const ( + insertAncestry = ` + INSERT INTO ancestry (name) VALUES ($1) RETURNING id` + + findAncestryID = `SELECT id FROM ancestry WHERE name = $1` + removeAncestry = `DELETE FROM ancestry WHERE name = $1` + + insertAncestryFeatures = ` + INSERT INTO ancestry_feature + (ancestry_layer_id, namespaced_feature_id, feature_detector_id, namespace_detector_id) VALUES + ($1, $2, $3, $4)` +) + +func FindAncestry(tx *sql.Tx, name string) (database.Ancestry, bool, error) { + var ( + ancestry = database.Ancestry{Name: name} + err error + ) + + id, ok, err := FindAncestryID(tx, name) + if !ok || err != nil { + return ancestry, ok, err + } + + if ancestry.By, err = FindAncestryDetectors(tx, id); err != nil { + return ancestry, false, err + } + + if ancestry.Layers, err = FindAncestryLayers(tx, id); err != nil { + return ancestry, false, err + } + + return ancestry, true, nil +} + +func UpsertAncestry(tx *sql.Tx, ancestry database.Ancestry) error { + if !ancestry.Valid() { + return database.ErrInvalidParameters + } + + if err := RemoveAncestry(tx, ancestry.Name); err != nil { + return err + } + + id, err := InsertAncestry(tx, ancestry.Name) + if err != nil { + return err + } + + detectorIDs, err := detector.FindDetectorIDs(tx, ancestry.By) + if err != nil { + return err + } + + // insert ancestry metadata + if err := InsertAncestryDetectors(tx, id, detectorIDs); err != nil { + return err + } + + layers := make([]string, 0, len(ancestry.Layers)) + for _, l := range ancestry.Layers { + layers = append(layers, l.Hash) + } + + layerIDs, ok, err := layer.FindLayerIDs(tx, layers) + if err != nil { + return err + } + + if !ok { + log.Error("layer cannot be found, this indicates that the internal logic of calling UpsertAncestry is wrong or the database is corrupted.") + return database.ErrMissingEntities + } + + ancestryLayerIDs, err := InsertAncestryLayers(tx, id, layerIDs) + if err != nil { + return err + } + + for i, id := range ancestryLayerIDs { + if err := InsertAncestryFeatures(tx, id, ancestry.Layers[i]); err != nil { + return err + } + } + + return nil +} + +func InsertAncestry(tx *sql.Tx, name string) (int64, error) { + var id int64 + err := tx.QueryRow(insertAncestry, name).Scan(&id) + if err != nil { + if util.IsErrUniqueViolation(err) { + return 0, util.HandleError("insertAncestry", errors.New("other Go-routine is processing this ancestry (skip)")) + } + + return 0, util.HandleError("insertAncestry", err) + } + + return id, nil +} + +func FindAncestryID(tx *sql.Tx, name string) (int64, bool, error) { + var id sql.NullInt64 + if err := tx.QueryRow(findAncestryID, name).Scan(&id); err != nil { + if err == sql.ErrNoRows { + return 0, false, nil + } + + return 0, false, util.HandleError("findAncestryID", err) + } + + return id.Int64, true, nil +} + +func RemoveAncestry(tx *sql.Tx, name string) error { + result, err := tx.Exec(removeAncestry, name) + if err != nil { + return util.HandleError("removeAncestry", err) + } + + affected, err := result.RowsAffected() + if err != nil { + return util.HandleError("removeAncestry", err) + } + + if affected != 0 { + log.WithField("ancestry", name).Debug("removed ancestry") + } + + return nil +} diff --git a/database/pgsql/ancestry/ancestry_detector.go b/database/pgsql/ancestry/ancestry_detector.go new file mode 100644 index 00000000..f51adf60 --- /dev/null +++ b/database/pgsql/ancestry/ancestry_detector.go @@ -0,0 +1,48 @@ +// Copyright 2019 clair authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ancestry + +import ( + "database/sql" + + "github.com/coreos/clair/database" + "github.com/coreos/clair/database/pgsql/detector" + "github.com/coreos/clair/database/pgsql/util" +) + +var selectAncestryDetectors = ` +SELECT d.name, d.version, d.dtype + FROM ancestry_detector, detector AS d + WHERE ancestry_detector.detector_id = d.id AND ancestry_detector.ancestry_id = $1;` + +var insertAncestryDetectors = ` + INSERT INTO ancestry_detector (ancestry_id, detector_id) + SELECT $1, $2 + WHERE NOT EXISTS (SELECT id FROM ancestry_detector WHERE ancestry_id = $1 AND detector_id = $2)` + +func FindAncestryDetectors(tx *sql.Tx, id int64) ([]database.Detector, error) { + detectors, err := detector.GetDetectors(tx, selectAncestryDetectors, id) + return detectors, err +} + +func InsertAncestryDetectors(tx *sql.Tx, ancestryID int64, detectorIDs []int64) error { + for _, detectorID := range detectorIDs { + if _, err := tx.Exec(insertAncestryDetectors, ancestryID, detectorID); err != nil { + return util.HandleError("insertAncestryDetectors", err) + } + } + + return nil +} diff --git a/database/pgsql/ancestry/ancestry_feature.go b/database/pgsql/ancestry/ancestry_feature.go new file mode 100644 index 00000000..33096d20 --- /dev/null +++ b/database/pgsql/ancestry/ancestry_feature.go @@ -0,0 +1,139 @@ +// Copyright 2019 clair authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ancestry + +import ( + "database/sql" + + "github.com/coreos/clair/database" + "github.com/coreos/clair/database/pgsql/detector" + "github.com/coreos/clair/database/pgsql/feature" + "github.com/coreos/clair/database/pgsql/util" + "github.com/coreos/clair/pkg/commonerr" +) + +const findAncestryFeatures = ` + SELECT namespace.name, namespace.version_format, feature.name, + feature.version, feature.version_format, feature_type.name, ancestry_layer.ancestry_index, + ancestry_feature.feature_detector_id, ancestry_feature.namespace_detector_id + FROM namespace, feature, feature_type, namespaced_feature, ancestry_layer, ancestry_feature + WHERE ancestry_layer.ancestry_id = $1 + AND feature_type.id = feature.type + AND ancestry_feature.ancestry_layer_id = ancestry_layer.id + AND ancestry_feature.namespaced_feature_id = namespaced_feature.id + AND namespaced_feature.feature_id = feature.id + AND namespaced_feature.namespace_id = namespace.id` + +func FindAncestryFeatures(tx *sql.Tx, ancestryID int64, detectors detector.DetectorMap) (map[int64][]database.AncestryFeature, error) { + // ancestry_index -> ancestry features + featureMap := make(map[int64][]database.AncestryFeature) + // retrieve ancestry layer's namespaced features + rows, err := tx.Query(findAncestryFeatures, ancestryID) + if err != nil { + return nil, util.HandleError("findAncestryFeatures", err) + } + + defer rows.Close() + + for rows.Next() { + var ( + featureDetectorID int64 + namespaceDetectorID int64 + feature database.NamespacedFeature + // index is used to determine which layer the feature belongs to. + index sql.NullInt64 + ) + + if err := rows.Scan( + &feature.Namespace.Name, + &feature.Namespace.VersionFormat, + &feature.Feature.Name, + &feature.Feature.Version, + &feature.Feature.VersionFormat, + &feature.Feature.Type, + &index, + &featureDetectorID, + &namespaceDetectorID, + ); err != nil { + return nil, util.HandleError("findAncestryFeatures", err) + } + + if feature.Feature.VersionFormat != feature.Namespace.VersionFormat { + // Feature must have the same version format as the associated + // namespace version format. + return nil, database.ErrInconsistent + } + + fDetector, ok := detectors.ByID[featureDetectorID] + if !ok { + return nil, database.ErrInconsistent + } + + nsDetector, ok := detectors.ByID[namespaceDetectorID] + if !ok { + return nil, database.ErrInconsistent + } + + featureMap[index.Int64] = append(featureMap[index.Int64], database.AncestryFeature{ + NamespacedFeature: feature, + FeatureBy: fDetector, + NamespaceBy: nsDetector, + }) + } + + return featureMap, nil +} + +func InsertAncestryFeatures(tx *sql.Tx, ancestryLayerID int64, layer database.AncestryLayer) error { + detectors, err := detector.FindAllDetectors(tx) + if err != nil { + return err + } + + nsFeatureIDs, err := feature.FindNamespacedFeatureIDs(tx, layer.GetFeatures()) + if err != nil { + return err + } + + // find the detectors for each feature + stmt, err := tx.Prepare(insertAncestryFeatures) + if err != nil { + return util.HandleError("insertAncestryFeatures", err) + } + + defer stmt.Close() + + for index, id := range nsFeatureIDs { + if !id.Valid { + return database.ErrMissingEntities + } + + namespaceDetectorID, ok := detectors.ByValue[layer.Features[index].NamespaceBy] + if !ok { + return database.ErrMissingEntities + } + + featureDetectorID, ok := detectors.ByValue[layer.Features[index].FeatureBy] + if !ok { + return database.ErrMissingEntities + } + + if _, err := stmt.Exec(ancestryLayerID, id, featureDetectorID, namespaceDetectorID); err != nil { + return util.HandleError("insertAncestryFeatures", commonerr.CombineErrors(err, stmt.Close())) + } + } + + return nil +} diff --git a/database/pgsql/ancestry/ancestry_layer.go b/database/pgsql/ancestry/ancestry_layer.go new file mode 100644 index 00000000..be053037 --- /dev/null +++ b/database/pgsql/ancestry/ancestry_layer.go @@ -0,0 +1,131 @@ +// Copyright 2019 clair authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ancestry + +import ( + "database/sql" + + "github.com/coreos/clair/database" + "github.com/coreos/clair/database/pgsql/detector" + "github.com/coreos/clair/database/pgsql/util" + "github.com/coreos/clair/pkg/commonerr" + log "github.com/sirupsen/logrus" +) + +const ( + findAncestryLayerHashes = ` + SELECT layer.hash, ancestry_layer.ancestry_index + FROM layer, ancestry_layer + WHERE ancestry_layer.ancestry_id = $1 + AND ancestry_layer.layer_id = layer.id + ORDER BY ancestry_layer.ancestry_index ASC` + insertAncestryLayers = ` + INSERT INTO ancestry_layer (ancestry_id, ancestry_index, layer_id) VALUES ($1, $2, $3) + RETURNING id` +) + +func FindAncestryLayerHashes(tx *sql.Tx, ancestryID int64) (map[int64]string, error) { + // retrieve layer indexes and hashes + rows, err := tx.Query(findAncestryLayerHashes, ancestryID) + if err != nil { + return nil, util.HandleError("findAncestryLayerHashes", err) + } + + layerHashes := map[int64]string{} + for rows.Next() { + var ( + hash string + index int64 + ) + + if err = rows.Scan(&hash, &index); err != nil { + return nil, util.HandleError("findAncestryLayerHashes", err) + } + + if _, ok := layerHashes[index]; ok { + // one ancestry index should correspond to only one layer + return nil, database.ErrInconsistent + } + + layerHashes[index] = hash + } + + return layerHashes, nil +} + +// insertAncestryLayers inserts the ancestry layers along with its content into +// the database. The layers are 0 based indexed in the original order. +func InsertAncestryLayers(tx *sql.Tx, ancestryID int64, layers []int64) ([]int64, error) { + stmt, err := tx.Prepare(insertAncestryLayers) + if err != nil { + return nil, util.HandleError("insertAncestryLayers", err) + } + + ancestryLayerIDs := []int64{} + for index, layerID := range layers { + var ancestryLayerID sql.NullInt64 + if err := stmt.QueryRow(ancestryID, index, layerID).Scan(&ancestryLayerID); err != nil { + return nil, util.HandleError("insertAncestryLayers", commonerr.CombineErrors(err, stmt.Close())) + } + + if !ancestryLayerID.Valid { + return nil, database.ErrInconsistent + } + + ancestryLayerIDs = append(ancestryLayerIDs, ancestryLayerID.Int64) + } + + if err := stmt.Close(); err != nil { + return nil, util.HandleError("insertAncestryLayers", err) + } + + return ancestryLayerIDs, nil +} + +func FindAncestryLayers(tx *sql.Tx, id int64) ([]database.AncestryLayer, error) { + detectors, err := detector.FindAllDetectors(tx) + if err != nil { + return nil, err + } + + layerMap, err := FindAncestryLayerHashes(tx, id) + if err != nil { + return nil, err + } + + featureMap, err := FindAncestryFeatures(tx, id, detectors) + if err != nil { + return nil, err + } + + layers := make([]database.AncestryLayer, len(layerMap)) + for index, layer := range layerMap { + // index MUST match the ancestry layer slice index. + if layers[index].Hash == "" && len(layers[index].Features) == 0 { + layers[index] = database.AncestryLayer{ + Hash: layer, + Features: featureMap[index], + } + } else { + log.WithFields(log.Fields{ + "ancestry ID": id, + "duplicated ancestry index": index, + }).WithError(database.ErrInconsistent).Error("ancestry layers with same ancestry_index is not allowed") + return nil, database.ErrInconsistent + } + } + + return layers, nil +} diff --git a/database/pgsql/ancestry_test.go b/database/pgsql/ancestry/ancestry_test.go similarity index 74% rename from database/pgsql/ancestry_test.go rename to database/pgsql/ancestry/ancestry_test.go index 7f0a37f8..992c80bf 100644 --- a/database/pgsql/ancestry_test.go +++ b/database/pgsql/ancestry/ancestry_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 clair authors +// Copyright 2019 clair authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package pgsql +package ancestry import ( "testing" @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/coreos/clair/database" + "github.com/coreos/clair/database/pgsql/testutil" ) var upsertAncestryTests = []struct { @@ -55,9 +56,9 @@ var upsertAncestryTests = []struct { title: "ancestry with invalid feature", in: &database.Ancestry{ Name: "a", - By: []database.Detector{realDetectors[1], realDetectors[2]}, + By: []database.Detector{testutil.RealDetectors[1], testutil.RealDetectors[2]}, Layers: []database.AncestryLayer{{Hash: "layer-1", Features: []database.AncestryFeature{ - {fakeNamespacedFeatures[1], fakeDetector[1], fakeDetector[2]}, + {testutil.FakeNamespacedFeatures[1], testutil.FakeDetector[1], testutil.FakeDetector[2]}, }}}, }, err: database.ErrMissingEntities.Error(), @@ -66,26 +67,27 @@ var upsertAncestryTests = []struct { title: "replace old ancestry", in: &database.Ancestry{ Name: "a", - By: []database.Detector{realDetectors[1], realDetectors[2]}, + By: []database.Detector{testutil.RealDetectors[1], testutil.RealDetectors[2]}, Layers: []database.AncestryLayer{ - {"layer-1", []database.AncestryFeature{{realNamespacedFeatures[1], realDetectors[2], realDetectors[1]}}}, + {"layer-1", []database.AncestryFeature{{testutil.RealNamespacedFeatures[1], testutil.RealDetectors[2], testutil.RealDetectors[1]}}}, }, }, }, } func TestUpsertAncestry(t *testing.T) { - store, tx := openSessionForTest(t, "UpsertAncestry", true) - defer closeTest(t, store, tx) + tx, cleanup := testutil.CreateTestTxWithFixtures(t, "TestUpsertAncestry") + defer cleanup() + for _, test := range upsertAncestryTests { t.Run(test.title, func(t *testing.T) { - err := tx.UpsertAncestry(*test.in) + err := UpsertAncestry(tx, *test.in) if test.err != "" { assert.EqualError(t, err, test.err, "unexpected error") return } assert.Nil(t, err) - actual, ok, err := tx.FindAncestry(test.in.Name) + actual, ok, err := FindAncestry(tx, test.in.Name) assert.Nil(t, err) assert.True(t, ok) database.AssertAncestryEqual(t, test.in, &actual) @@ -113,16 +115,17 @@ var findAncestryTests = []struct { in: "ancestry-2", err: "", ok: true, - ancestry: takeAncestryPointerFromMap(realAncestries, 2), + ancestry: testutil.TakeAncestryPointerFromMap(testutil.RealAncestries, 2), }, } func TestFindAncestry(t *testing.T) { - store, tx := openSessionForTest(t, "FindAncestry", true) - defer closeTest(t, store, tx) + tx, cleanup := testutil.CreateTestTxWithFixtures(t, "TestFindAncestry") + defer cleanup() + for _, test := range findAncestryTests { t.Run(test.title, func(t *testing.T) { - ancestry, ok, err := tx.FindAncestry(test.in) + ancestry, ok, err := FindAncestry(tx, test.in) if test.err != "" { assert.EqualError(t, err, test.err, "unexpected error") return