// Copyright (C) Stichting Deltares 2017. All rights reserved.
//
// This file is part of Ringtoets.
//
// Ringtoets is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
// All names, logos, and references to "Deltares" are registered trademarks of
// Stichting Deltares and remain full property of Stichting Deltares at all times.
// All rights reserved.
using System;
using System.Data;
using System.Data.SQLite;
using Core.Common.Base.IO;
using Core.Common.IO.Readers;
using Core.Common.Utils.Builders;
using Ringtoets.MacroStabilityInwards.IO.Builders;
using Ringtoets.MacroStabilityInwards.IO.Exceptions;
using Ringtoets.MacroStabilityInwards.IO.Properties;
using Ringtoets.MacroStabilityInwards.IO.SoilProfile.Schema;
using Ringtoets.MacroStabilityInwards.Primitives;
namespace Ringtoets.MacroStabilityInwards.IO.SoilProfile
{
///
/// This class reads a SqLite database file and constructs instances from this database.
///
public class MacroStabilityInwardsSoilProfileReader : SqLiteDatabaseReaderBase, IRowBasedDatabaseReader
{
private const string mechanismName = "Piping";
private const string mechanismParameterName = "mechanism";
private IDataReader dataReader;
///
/// Creates a new instance of which will use the
/// as its source. The reader will not point to any record at the start. Use to start reading
/// profiles.
///
/// The path of the database file to open.
/// Thrown when:
///
/// - The contains invalid characters.
/// - No file could be found at .
/// - Preparing the queries to read from the database failed.
///
///
public MacroStabilityInwardsSoilProfileReader(string databaseFilePath) : base(databaseFilePath)
{
VerifyVersion(databaseFilePath);
InitializeReader();
}
///
/// Gets the total number of profiles that can be read from the database.
///
public int Count { get; private set; }
///
/// Gets a value indicating whether or not more soil profiles can be read using
/// the .
///
public bool HasNext { get; private set; }
///
/// Reads the information for the next profile from the database and creates a
/// instance of the information.
///
/// The next from the database, or null if no more profiles can be read.
/// Thrown when reading the profile in the database contained a non-parsable geometry.
/// Thrown when the database returned incorrect values for required properties.
public MacroStabilityInwardsSoilProfile1D ReadProfile()
{
if (!HasNext)
{
return null;
}
try
{
return ReadMacroStabilityInwardsSoilProfile();
}
catch (InvalidCastException e)
{
string message = new FileReaderErrorMessageBuilder(Path).Build(Resources.MacroStabilityInwardsSoilProfileReader_Critical_Unexpected_value_on_column);
throw new CriticalFileReadException(message, e);
}
}
protected override void Dispose(bool disposing)
{
dataReader?.Dispose();
base.Dispose(disposing);
}
///
/// Moves the reader to the next record in the database.
///
public void MoveNext()
{
HasNext = MoveNext(dataReader);
}
///
/// Reads the value in the column with name from the
/// current row that's being pointed at.
///
/// The type of object to read.
/// The name of the column to read from.
/// The value in the column, or null if the value was .
/// Thrown when the value in the column could not be casted to type .
public T ReadOrDefault(string columnName)
{
object valueObject = dataReader[columnName];
if (valueObject.Equals(DBNull.Value))
{
return default(T);
}
return (T) valueObject;
}
///
/// Reads a value at column from the database.
///
/// The expected type of value in the column with name .
/// The name of the column to read from.
/// The read value from the column with name .
/// Thrown when the value in the column was not of type .
public T Read(string columnName)
{
return (T) dataReader[columnName];
}
private void VerifyVersion(string databaseFilePath)
{
using (var versionReader = new SoilDatabaseVersionReader(databaseFilePath))
{
try
{
versionReader.VerifyVersion();
}
catch (CriticalFileReadException)
{
CloseConnection();
throw;
}
}
}
///
/// Reads a from the database.
///
/// A new .
/// Thrown when a recoverable error occurred while reading from the database.
/// Thrown when recovering from the failed.
private MacroStabilityInwardsSoilProfile1D ReadMacroStabilityInwardsSoilProfile()
{
try
{
var dimensionValue = Read(SoilProfileTableColumns.Dimension);
return dimensionValue == 1 ? SoilProfile1DReader.ReadFrom(this) : SoilProfile2DReader.ReadFrom(this);
}
catch (MacroStabilityInwardsSoilProfileReadException e)
{
MoveToNextProfile(e.ProfileName);
throw;
}
}
///
/// Steps through the result rows until a row is read which' profile name differs from .
///
/// The name of the profile to skip.
private void MoveToNextProfile(string profileName)
{
while (HasNext && Read(SoilProfileTableColumns.ProfileName).Equals(profileName))
{
MoveNext();
}
}
///
/// Prepares a new data reader with queries for obtaining the profiles and updates the reader
/// so that it points to the first row of the result set.
///
/// Thrown when the amount of profiles in database could not be read.
/// A query could not be executed on the database schema.
private void InitializeReader()
{
try
{
PrepareReader();
GetCount();
}
catch (SQLiteException e)
{
dataReader?.Dispose();
string message = new FileReaderErrorMessageBuilder(Path).Build(Resources.Error_SoilProfile_read_from_database);
throw new CriticalFileReadException(message, e);
}
MoveNext();
}
///
/// Prepares the two queries required for obtaining all the SoilProfile1D and SoilProfile2D with an x defined
/// to take an intersection from. Since two separate queries are used, the will
/// have two result sets which the method takes into account.
///
/// A query could not be executed on the database schema.
private void PrepareReader()
{
string countQuery = SoilDatabaseQueryBuilder.GetMacroStabilityInwardsSoilProfileCountQuery();
string subQueryGetNumberOfLayerProfile1D =
"SELECT " +
"SP1D_ID, " +
$"COUNT(*) as {SoilProfileTableColumns.LayerCount} " +
"FROM SoilLayer1D " +
"GROUP BY SP1D_ID";
string subQueryGetNumberOfLayerProfile2D =
$"SELECT SP2D_ID, COUNT(*) as {SoilProfileTableColumns.LayerCount} " +
"FROM SoilLayer2D " +
"GROUP BY SP2D_ID";
string subQueryGetMaterialPropertiesOfLayer =
"SELECT " +
"mat.MA_ID, " +
$"mat.MA_Name as {SoilProfileTableColumns.MaterialName}, " +
$"max(case when pn.PN_Name = 'Color' then pv.PV_Value end) {SoilProfileTableColumns.Color}, " +
$"max(case when pn.PN_Name = 'BelowPhreaticLevelStochast' then s.ST_Dist_Type end) {SoilProfileTableColumns.BelowPhreaticLevelDistribution}, " +
$"max(case when pn.PN_Name = 'BelowPhreaticLevelStochast' then s.ST_Shift end) {SoilProfileTableColumns.BelowPhreaticLevelShift}, " +
$"max(case when pn.PN_Name = 'BelowPhreaticLevelStochast' then s.ST_Mean end) {SoilProfileTableColumns.BelowPhreaticLevelMean}, " +
$"max(case when pn.PN_Name = 'BelowPhreaticLevelStochast' then s.ST_Deviation end) {SoilProfileTableColumns.BelowPhreaticLevelDeviation}, " +
$"max(case when pn.PN_Name = 'PermeabKxStochast' then s.ST_Dist_Type end) {SoilProfileTableColumns.PermeabilityDistribution}, " +
$"max(case when pn.PN_Name = 'PermeabKxStochast' then s.ST_Shift end) {SoilProfileTableColumns.PermeabilityShift}, " +
$"max(case when pn.PN_Name = 'PermeabKxStochast' then s.ST_Mean end) {SoilProfileTableColumns.PermeabilityMean}, " +
$"max(case when pn.PN_Name = 'PermeabKxStochast' then s.ST_Variation end) {SoilProfileTableColumns.PermeabilityCoefficientOfVariation}, " +
$"max(case when pn.PN_Name = 'DiameterD70Stochast' then s.ST_Dist_Type end) {SoilProfileTableColumns.DiameterD70Distribution}, " +
$"max(case when pn.PN_Name = 'DiameterD70Stochast' then s.ST_Shift end) {SoilProfileTableColumns.DiameterD70Shift}, " +
$"max(case when pn.PN_Name = 'DiameterD70Stochast' then s.ST_Mean end) {SoilProfileTableColumns.DiameterD70Mean}, " +
$"max(case when pn.PN_Name = 'DiameterD70Stochast' then s.ST_Variation end) {SoilProfileTableColumns.DiameterD70CoefficientOfVariation} " +
"FROM ParameterNames AS pn " +
"LEFT JOIN ParameterValues AS pv USING(PN_ID) " +
"LEFT JOIN Stochast AS s USING(PN_ID) " +
"JOIN Materials AS mat " +
"WHERE pv.MA_ID = mat.MA_ID OR s.MA_ID = mat.MA_ID " +
"GROUP BY mat.MA_ID ";
string subQueryGetLayerPropertiesOfLayer1D =
"SELECT " +
"SL1D_ID, " +
$"PV_Value as {SoilProfileTableColumns.IsAquifer} " +
"FROM ParameterNames " +
"JOIN LayerParameterValues USING(PN_ID) " +
$"WHERE PN_NAME = '{SoilProfileTableColumns.IsAquifer}'";
string subQueryGetLayerPropertiesOfLayer2D =
"SELECT " +
"SL2D_ID, " +
$"PV_Value as {SoilProfileTableColumns.IsAquifer} " +
"FROM ParameterNames " +
"JOIN LayerParameterValues USING(PN_ID) " +
$"WHERE PN_NAME = '{SoilProfileTableColumns.IsAquifer}'";
string query1D =
"SELECT " +
$"1 AS {SoilProfileTableColumns.Dimension}, " +
$"sp1d.SP1D_Name AS {SoilProfileTableColumns.ProfileName}, " +
$"layerCount.{SoilProfileTableColumns.LayerCount}, " +
$"sp1d.BottomLevel AS {SoilProfileTableColumns.Bottom}, " +
$"sl1d.TopLevel AS {SoilProfileTableColumns.Top}, " +
$"{SoilProfileTableColumns.MaterialName}, " +
$"{SoilProfileTableColumns.IsAquifer}, " +
$"{SoilProfileTableColumns.Color}, " +
$"{SoilProfileTableColumns.BelowPhreaticLevelDistribution}, " +
$"{SoilProfileTableColumns.BelowPhreaticLevelShift}, " +
$"{SoilProfileTableColumns.BelowPhreaticLevelMean}, " +
$"{SoilProfileTableColumns.BelowPhreaticLevelDeviation}, " +
$"{SoilProfileTableColumns.DiameterD70Distribution}, " +
$"{SoilProfileTableColumns.DiameterD70Shift}, " +
$"{SoilProfileTableColumns.DiameterD70Mean}, " +
$"{SoilProfileTableColumns.DiameterD70CoefficientOfVariation}, " +
$"{SoilProfileTableColumns.PermeabilityDistribution}, " +
$"{SoilProfileTableColumns.PermeabilityShift}, " +
$"{SoilProfileTableColumns.PermeabilityMean}, " +
$"{SoilProfileTableColumns.PermeabilityCoefficientOfVariation}, " +
$"sp1d.SP1D_ID AS {SoilProfileTableColumns.SoilProfileId} " +
"FROM Mechanism AS m " +
"JOIN Segment AS segment USING(ME_ID) " +
"JOIN (SELECT SSM_ID, SP1D_ID, SP2D_ID FROM StochasticSoilProfile GROUP BY SSM_ID, SP1D_ID, SP2D_ID) ssp USING(SSM_ID) " +
"JOIN SoilProfile1D sp1d USING (SP1D_ID) " +
"JOIN (" +
subQueryGetNumberOfLayerProfile1D +
") layerCount USING (SP1D_ID) " +
"JOIN SoilLayer1D sl1d USING (SP1D_ID) " +
"LEFT JOIN (" +
subQueryGetMaterialPropertiesOfLayer +
") materialProperties USING(MA_ID) " +
"LEFT JOIN (" +
subQueryGetLayerPropertiesOfLayer1D +
") layerProperties USING(SL1D_ID) " +
$"WHERE m.{MechanismTableColumns.MechanismName} = @{MechanismTableColumns.MechanismName} " +
"GROUP BY sp1d.SP1D_ID, sl1d.SL1D_ID;";
string query2D =
"SELECT " +
$"2 as {SoilProfileTableColumns.Dimension}, " +
$"sp2d.SP2D_Name as {SoilProfileTableColumns.ProfileName}, " +
$"layerCount.{SoilProfileTableColumns.LayerCount}, " +
$"sl2d.GeometrySurface as {SoilProfileTableColumns.LayerGeometry}, " +
$"mpl.X as {SoilProfileTableColumns.IntersectionX}, " +
$"{SoilProfileTableColumns.MaterialName}, " +
$"{SoilProfileTableColumns.IsAquifer}, " +
$"{SoilProfileTableColumns.Color}, " +
$"{SoilProfileTableColumns.BelowPhreaticLevelDistribution}, " +
$"{SoilProfileTableColumns.BelowPhreaticLevelShift}, " +
$"{SoilProfileTableColumns.BelowPhreaticLevelMean}, " +
$"{SoilProfileTableColumns.BelowPhreaticLevelDeviation}, " +
$"{SoilProfileTableColumns.DiameterD70Distribution}, " +
$"{SoilProfileTableColumns.DiameterD70Shift}, " +
$"{SoilProfileTableColumns.DiameterD70Mean}, " +
$"{SoilProfileTableColumns.DiameterD70CoefficientOfVariation}, " +
$"{SoilProfileTableColumns.PermeabilityDistribution}, " +
$"{SoilProfileTableColumns.PermeabilityShift}, " +
$"{SoilProfileTableColumns.PermeabilityMean}, " +
$"{SoilProfileTableColumns.PermeabilityCoefficientOfVariation}, " +
$"sp2d.SP2D_ID as {SoilProfileTableColumns.SoilProfileId} " +
"FROM Mechanism AS m " +
"JOIN Segment AS segment USING(ME_ID) " +
"JOIN (SELECT SSM_ID, SP1D_ID, SP2D_ID FROM StochasticSoilProfile GROUP BY SSM_ID, SP1D_ID, SP2D_ID) ssp USING(SSM_ID) " +
"JOIN SoilProfile2D sp2d USING (SP2D_ID) " +
"JOIN (" +
subQueryGetNumberOfLayerProfile2D +
") layerCount USING (SP2D_ID) " +
"JOIN SoilLayer2D sl2d USING (SP2D_ID) " +
"LEFT JOIN MechanismPointLocation mpl USING(ME_ID, SP2D_ID) " +
"LEFT JOIN (" +
subQueryGetMaterialPropertiesOfLayer +
") materialProperties USING(MA_ID) " +
"LEFT JOIN (" +
subQueryGetLayerPropertiesOfLayer2D +
") layerProperties USING(SL2D_ID) " +
$"WHERE m.{MechanismTableColumns.MechanismName} = @{MechanismTableColumns.MechanismName} " +
"GROUP BY sp2d.SP2D_ID, sl2d.SL2D_ID;";
dataReader = CreateDataReader(countQuery + query2D + query1D, new SQLiteParameter
{
DbType = DbType.String,
Value = mechanismName,
ParameterName = mechanismParameterName
}, new SQLiteParameter
{
DbType = DbType.String,
ParameterName = $"@{MechanismTableColumns.MechanismName}",
Value = mechanismName
});
}
private void GetCount()
{
dataReader.Read();
Count = (int) Read(SoilProfileTableColumns.ProfileCount);
dataReader.NextResult();
}
}
}