tdb.ps1

<#PSScriptInfo
.VERSION 1.0.5
.GUID 4ee202bc-b16f-46b4-a15b-72ae9f4ae177
.AUTHOR voytas75
.TAGS text database, database, simple, minimal
.PROJECTURI https://github.com/voytas75/tdb
.ICONURI https://raw.githubusercontent.com/voytas75/tdb/master/images/tdb.png
.EXTERNALMODULEDEPENDENCIES
.RELEASENOTES
1.0.5: add iconURI.
1.0.4: change function names, fixes in Insert and Update record.
1.0.3: added listing tables.
1.0.2: Improved modularity, added centralized error handling and logging. Enhanced documentation with detailed help blocks and a comprehensive usage guide. Included unit tests for all critical functions and ensured compatibility with different environments.
1.0.1: initializing.
#>


<#
.SYNOPSIS
Simple Miniature Text Relational Database Management System (TDB)
 
.DESCRIPTION
This PowerShell script implements a simple text-based relational DBMS. It supports CRUD operations, search functionality, indexing, and data integrity checks.
The script is designed to be clean, efficient, and functional, adhering to best practices.
 
.PARAMETER configFilePath
Specifies the path to the configuration file. If not provided, the script will attempt to load the default configuration file from the script's directory.
#>


param (
    [Parameter(Mandatory = $false)]
    [string]$configFilePath
)

#region Script Metadata
# Define the current version of the script
$tdbVersion = "1.0.5"

# Get the script name from the invocation without the extension
$scriptname = [System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath)

# Default columns setting (system application setting)
$DefaultColumns = @("ID", "CreationTime")
#endregion Script Metadata

#region Functions
function Get-LatestVersion {
    param (
        [Parameter(Mandatory = $true)]
        [string]$scriptName
    )
  
    try {
        # Find the script on PowerShell Gallery
        $scriptInfo = Find-Script -Name $scriptName -ErrorAction Stop
  
        # Return the latest version
        return $scriptInfo.Version
    }
    catch [System.Exception] {
        $functionName = $MyInvocation.MyCommand.Name
        #Write-Warning "Error occurred while trying to find the script '$scriptName' on PowerShell Gallery."
        Update-ErrorHandling -ErrorRecord $_ -ErrorContext "$functionName function" -LogFilePath $config.LogFilePath  
        return
    }
}

function Get-CheckForScriptUpdate {
    param (
        [Parameter(Mandatory = $true)]
        [version]$currentScriptVersion,
        
        [Parameter(Mandatory = $true)]
        [string]$scriptName
    )
    try {
        # Retrieve the latest version of the script
        $latestScriptVersion = Get-LatestVersion -scriptName $scriptName
        if ($latestScriptVersion) {
            # Compare the current version with the latest version
            if (([version]$currentScriptVersion) -lt [version]$latestScriptVersion) {
                Write-Host " A new version ($latestScriptVersion) of $scriptName is available. You are currently using version $currentScriptVersion. " -BackgroundColor DarkYellow -ForegroundColor Blue
                write-Host "`n`n"
            } 
        }
        #else {
        # Write-Warning "Failed to check for the latest version of the script."
        #}
    }
    catch [System.Exception] {
        $functionName = $MyInvocation.MyCommand.Name
        Update-ErrorHandling -ErrorRecord $_ -ErrorContext "$functionName function" -LogFilePath $config.LogFilePath 
    }

}

function Show-Banner {
    Write-Host @'
 
   __powershell___________/\\\___/\\\________
    ___voytas75___________\/\\\__\/\\\________
     _____/\\\_____________\/\\\__\/\\\________
      __/\\\\\\\\\\\________\/\\\__\/\\\________
       _\////\\\////____/\\\\\\\\\__\/\\\\\\\\\__
        ____\/\\\_______/\\\////\\\__\/\\\////\\\_
         ____\/\\\_/\\__\/\\\__\/\\\__\/\\\__\/\\\_
          ____\//\\\\\___\//\\\\\\\/\\_\/\\\\\\\\\__
           _____\/////_____\///////\//__\/////////___
    
           powershell [t]ext [d]ata[b]ase
           https://github.com/voytas75/tdb
 
 
'@

}

# Logging function
function Write-tdbLog {
    param (
        [string]$Message,
        [string]$Level = "INFO"
    )
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logDir = Split-Path -Path $config.LogFilePath -Parent
    if (-not (Test-Path -Path $logDir)) {
        New-Item -Path $logDir -ItemType Directory -Force
    }
    "$timestamp [$Level] - $Message" | Out-File -FilePath $config.LogFilePath -Append -Force
}

# Error handling function
function Handle-tdbError {
    param (
        [string]$ErrorMessage
    )
    Write-tdbLog -Message $ErrorMessage -Level "ERROR"
    throw $ErrorMessage
}

function Update-ErrorHandling {
    param (
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.ErrorRecord]$ErrorRecord,

        [string]$ErrorContext,

        [string]$LogFilePath
    )

    # Capture detailed error information
    $errorDetails = [ordered]@{
        Timestamp         = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        ErrorMessage      = $ErrorRecord.Exception.Message
        ExceptionType     = $ErrorRecord.Exception.GetType().FullName
        ErrorContext      = $ErrorContext
        ScriptFullName    = $MyInvocation.ScriptName
        LineNumber        = $MyInvocation.ScriptLineNumber
        StackTrace        = $ErrorRecord.ScriptStackTrace
        UserName          = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
        MachineName       = $env:COMPUTERNAME
        PowerShellVersion = $PSVersionTable.PSVersion.ToString()

    } | ConvertTo-Json

    # Provide suggestions based on the error type
    $suggestions = switch -Regex ($ErrorMessage) {
        "Get-tdbRecord" {
            "Check the table name and ensure it exists. Verify the filter and logical operator parameters."
        }
        "New-tdbTable" {
            "Ensure the table name is unique and follows the naming conventions. Verify the columns provided."
        }
        "Insert-tdbRecord" {
            "Check if the table exists and the record format is correct. Ensure the ID field is not manually set."
        }
        "Update-tdbRecord" {
            "Verify the table name, filter criteria, and new values. Ensure the table exists and the filter matches records."
        }
        "Remove-tdbRecord" {
            "Ensure the table exists and the filter criteria are correct. Verify that matching records exist."
        }
        "New-tdbIndex" {
            "Check if the index name is unique and the table and column names are correct. Ensure the table exists."
        }
        "Reindex-tdbIndex" {
            "Verify the index name and ensure it exists. Check the table and column names in the index metadata."
        }
        "UnauthorizedAccessException" {
            "Check the file permissions and ensure you have the necessary access rights to the file or directory."
        }
        "IOException" {
            "Ensure the file path is correct and the file is not being used by another process."
        }
        default {
            "Refer to the error message and stack trace for more details. Consult the official documentation or seek help from the community."
        }
    }

    # Display the error details and suggestions
    #Write-Host "-- Error: $($ErrorRecord.Exception.Message)"
    Write-Host "-- Error: $($ErrorRecord.Exception.Message)"
    Write-Host "-- Context: $ErrorContext"
    Write-Host "-- Suggestions: $suggestions"

    # Log the error details if LogFilePath is provided
    if ($LogFilePath) {
        $errorDetails | Out-File -FilePath $LogFilePath -Append -Force
        if (Test-Path -Path $LogFilePath) {
            Write-Host ">> Error details have been saved to the file: $LogFilePath" -ForegroundColor Yellow
        }
        else {
            Write-Host "-- The specified log file path does not exist: $LogFilePath" -ForegroundColor Red
        }
    }        
}

# Function to create a new table
function New-tdbTable {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidatePattern("^[a-zA-Z0-9_]+$")]  # Only allow alphanumeric and underscore
        [string]$TableName,
        [Parameter(Mandatory)]
        [string[]]$Columns
    )
    try {
        $tablePath = Join-Path -Path $config.DBDirectory -ChildPath "$TableName.csv"
        
        # Check if the table already exists
        if (Test-Path -Path $tablePath) {
            Handle-tdbError -ErrorMessage "Table '$TableName' already exists."
        }

        # Combine default columns with user-defined columns, ensuring no duplicates
        $allColumns = $DefaultColumns + ($Columns | Where-Object { $DefaultColumns -notcontains $_ })
        $header = $allColumns -join ","
        
        # Ensure the directory exists
        $dbDir = Split-Path -Path $tablePath -Parent
        if (-not (Test-Path -Path $dbDir)) {
            New-Item -Path $dbDir -ItemType Directory -Force
        }
        
        $header | Out-File -FilePath $tablePath -Force
        Write-tdbLog -Message "Table '$TableName' created with columns: $allColumns"
    }
    catch {
        $functionName = $MyInvocation.MyCommand.Name
        Update-ErrorHandling -ErrorRecord $_ -ErrorContext "$functionName function; Error creating table '$TableName'" -LogFilePath $config.LogFilePath  
    }
}

# Function to list all tables in the current database or show info for a specific table
function Get-tdbTable {
    [CmdletBinding()]
    param (
        [string]$TableName
    )
    try {
        Write-Verbose "Retrieving database directory from configuration."
        # Get the database directory from the configuration
        $dbDir = $config.DBDirectory
        
        Write-Verbose "Checking if the database directory exists."
        # Check if the database directory exists
        if (-not (Test-Path -Path $dbDir)) {
            Handle-tdbError -ErrorMessage "Database directory '$dbDir' does not exist."
        }

        if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey('TableName')) {
            Write-Verbose "Retrieving information for table '$TableName'."
            # Show info for the provided table
            $tablePath = Join-Path -Path $dbDir -ChildPath "$TableName.csv"
            if (-not (Test-Path -Path $tablePath)) {
                Write-Output "Table '$TableName' does not exist."
                #Handle-tdbError -ErrorMessage "Table '$TableName' does not exist."
                return
            }
            $tableSize = (Get-Item $tablePath).Length
            $tableInfo = [PSCustomObject]@{
                TableName   = $TableName
                CreatedOn   = (Get-Item $tablePath).CreationTime
                ModifiedOn  = (Get-Item $tablePath).LastWriteTime
                SizeInBytes = $tableSize
            }
            Write-Output "Table: $($tableInfo.TableName), Created On: $($tableInfo.CreatedOn), Last Modified On: $($tableInfo.ModifiedOn), Size: $($tableInfo.SizeInBytes) bytes"
        }
        else {
            Write-Verbose "Retrieving list of all tables in the database."
            # Get all CSV files in the database directory, which represent tables
            $tables = Get-ChildItem -Path $dbDir -Filter *.csv | ForEach-Object {
                $tablePath = $_.FullName
                $tableSize = (Get-Item $tablePath).Length
                [PSCustomObject]@{
                    TableName   = $_.BaseName
                    CreatedOn   = $_.CreationTime
                    ModifiedOn  = $_.LastWriteTime
                    SizeInBytes = $tableSize
                }
            }

            # Output the list of tables or a message if no tables are found
            if ($tables.Count -eq 0) {
                Write-Output "No tables found in the database."
            }
            else {
                Write-Output "Tables in the database:"
                $tables | ForEach-Object { 
                    Write-Output "Table: $($_.TableName), Created On: $($_.CreatedOn), Last Modified On: $($_.ModifiedOn), Size: $($_.SizeInBytes) bytes"
                }
            }
        }

        Write-Verbose "Showing context."
        # Show context of config file path
        Write-Output "Configuration file path: $configFilePath"
    }
    catch {
        # Handle any errors that occur during the process
        $functionName = $MyInvocation.MyCommand.Name
        Update-ErrorHandling -ErrorRecord $_ -ErrorContext "$functionName function; Error retrieving tables from database" -LogFilePath $config.LogFilePath  
    }
}



# Function to insert a new record
function Insert-tdbRecord {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]$TableName,
        [Parameter(Mandatory)]
        [hashtable]$Record
    )
    try {
        Write-Verbose "Starting to insert record into table '$TableName'."
        $tablePath = Join-Path -Path $config.DBDirectory -ChildPath "$TableName.csv"
        if (-not (Test-Path -Path $tablePath)) {
            $errorMessage = "Table '$TableName' does not exist. Please ensure the table name is correct and the table has been created."
            Write-Output $errorMessage
            #Handle-tdbError -ErrorMessage $errorMessage
            return
        }

        Write-Verbose "Reading columns from table '$TableName'."
        $columns = (Get-Content -Path $tablePath -First 1) -split ","
        
        # Get the current max ID and increment it
        Write-Verbose "Importing data from table '$TableName'."
        $data = Import-Csv -Path $tablePath
        $maxId = if ($data) { [int]($data | Measure-Object -Property ID -Maximum).Maximum } else { 0 }
        $newId = $maxId + 1
        Write-Verbose "New ID for the record will be $newId."

        # Check if user provided ID and show info that ID is auto-incrementing
        if ($Record.ContainsKey("ID")) {
            Write-Warning "The 'ID' field is auto-incrementing and will be $newId."
        }

        # Add auto-increment ID and CreationTime to the record
        $Record["ID"] = $newId
        $Record["CreationTime"] = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss.fff")

        Write-Verbose "Preparing values for the new record."
        $values = foreach ($col in $columns) { 
            if ($Record.ContainsKey($col)) { $Record[$col] } else { $null } 
        }

        $line = ($values -join ",")
        Write-Verbose "Appending new record to table '$TableName'."
        Add-Content -Path $tablePath -Value $line
        Write-tdbLog -Message "Inserted record into '$TableName': $Record"

        # Update indexes if they exist
        $indexDir = Join-Path -Path $config.DBDirectory -ChildPath "Indexes"
        $indexFiles = Get-ChildItem -Path $indexDir -Filter "*.index" -ErrorAction SilentlyContinue

        foreach ($indexFile in $indexFiles) {
            Write-Verbose "Checking index file: $($indexFile.Name)"
            $indexData = Import-Clixml -Path $indexFile.FullName
            if ($indexData.TableName -eq $TableName) {
                Write-Verbose "Index file $($indexFile.Name) matches table $TableName. Updating index."
                
                # Check if the Records property exists and is an array
                if (-not $indexData.PSObject.Properties['Records']) {
                    $indexData | Add-Member -MemberType NoteProperty -Name 'Records' -Value @()
                }
                elseif (-not ($indexData.Records -is [System.Collections.ArrayList])) {
                    $indexData.Records = [System.Collections.ArrayList]::new($indexData.Records)
                }

                # Ensure the Records property is an ArrayList to allow adding new items
                if ($indexData.Records -is [System.Collections.ArrayList]) {
                    $indexData.Records.Add($Record)
                }
                else {
                    $tempArrayList = [System.Collections.ArrayList]::new()
                    $tempArrayList.AddRange($indexData.Records)
                    $tempArrayList.Add($Record)
                    $indexData.Records = $tempArrayList
                }

                Write-Verbose "Exporting updated index data to file $($indexFile.FullName)."
                $indexData | Export-Clixml -Path $indexFile.FullName -Force
                Write-tdbLog -Message "Index '$($indexFile.BaseName)' updated with new record."
            }
            else {
                Write-Verbose "Index file $($indexFile.Name) does not match table $TableName. Skipping."
            }
        }
        Write-Verbose "Finished inserting record into table '$TableName'."
    }
    catch {
        $functionName = $MyInvocation.MyCommand.Name
        Update-ErrorHandling -ErrorRecord $_ -ErrorContext "$functionName function; Error inserting record into '$TableName'" -LogFilePath $config.LogFilePath  
    }
}

# Function to read records
function Get-tdbRecord {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]

        [string]$TableName,

        [hashtable]$Filter,

        [string]$LogicalOperator = 'AND',

        [ValidateSet("equals", "contains", "starts with", "ends with")]
        [string]$ComparisonOperator = 'equals'
    )
    try {
        Write-Verbose "Starting Get-tdbRecord for table '$TableName' with filter and logical operator '$LogicalOperator'."
        Write-Debug "Parameters: TableName=$TableName, Filter=$($Filter | Out-String), LogicalOperator=$LogicalOperator, ComparisonOperator=$ComparisonOperator"
        
        $tablePath = Join-Path -Path $config.DBDirectory -ChildPath "$TableName.csv"
        Write-Debug "Table path: $tablePath"
        
        if (-not (Test-Path -Path $tablePath)) {
            Handle-tdbError -ErrorMessage "Table '$TableName' does not exist."
        }

        $indexDir = Join-Path -Path $config.DBDirectory -ChildPath "Indexes"
        Write-Debug "Index directory: $indexDir"
        
        $indexFiles = Get-ChildItem -Path $indexDir -Filter "*.index" -ErrorAction SilentlyContinue
        Write-Debug "Index files found: $($indexFiles | Out-String)"
        
        $indexUsed = $false

        foreach ($indexFile in $indexFiles) {
            $indexData = Import-Clixml -Path $indexFile.FullName
            Write-Debug "Checking index file: $($indexFile.FullName)"
            Write-Debug "Checking if index file matches table and filter keys: TableName=$($indexData.TableName), FilterKeys=$($Filter.Keys)"
            Write-Debug "Index data: $($indexData | Out-String)"
            if ($indexData.TableName -eq $TableName -and $Filter.Keys -contains $indexData.ColumnName) {
                $indexPath = $indexFile.FullName
                $indexUsed = $true
                Write-Debug "Index file $($indexFile.FullName) matches table $TableName and filter keys. Using this index."
                break
            }
        }

        if ($indexUsed -and (Test-Path -Path $indexPath)) {
            Write-Verbose "Using index file: $indexPath"
            $indexData = Import-Clixml -Path $indexPath
            Write-Debug "Index data imported: $($indexData | Out-String)"
            
            $data = $indexData.Records | Where-Object {
                $result = $true
                $record = $_
                Write-Debug "Evaluating record: $($record | Out-String)"
                
                foreach ($key in $Filter.Keys) {
                    if (-not $record.PSObject.Properties.Match($key)) {
                        Write-Warning "The property '$key' cannot be found in index. Skipping this filter."
                        continue
                    }
                    $value = $Filter[$key]
                    Write-Debug "Filtering with key: $key, value: $value"
                    
                    $conditionMet = switch ($ComparisonOperator) {
                        "equals" { $record.$key -eq $value }
                        "contains" { $record.$key -like "*$value*" }
                        "starts with" { $record.$key -like "$value*" }
                        "ends with" { $record.$key -like "*$value" }
                        default { throw "Unsupported comparison operator: $ComparisonOperator" }
                    }
                    Write-Debug "Condition met: $conditionMet"
                    
                    if ($LogicalOperator -eq 'OR') {
                        $result = $result -or $conditionMet
                        if ($result) { break }
                    }
                    else {
                        $result = $result -and $conditionMet
                    }
                }
                Write-Debug "Result for record: $result"
                return $result
            }
        }
        else {
            Write-Verbose "No suitable index found. Performing full table scan."
            $data = Import-Csv -Path $tablePath
            Write-Verbose "Data imported from $tablePath"
            Write-Debug "Imported data: $($data | Out-String)"

            if ($Filter) {
                $data = $data | Where-Object {
                    $result = $true
                    $record = $_
                    Write-Debug "Evaluating record: $($record | Out-String)"
                    
                    foreach ($key in $Filter.Keys) {
                        if (-not $record.PSObject.Properties.Match($key)) {
                            Write-Warning "The property '$key' cannot be found on table '$tablePath'. Skipping this filter."
                            continue
                        }
                        $value = $Filter[$key]
                        Write-Debug "Filtering with key: $key, value: $value"
                        
                        $conditionMet = switch ($ComparisonOperator) {
                            "equals" { 
                                if ($record.PSObject.Properties.Match($key)) {
                                    Write-Verbose "Comparing property '$key' '$record' with value '$value'"
                                    $record.$key -eq $value
                                }
                                else {
                                    Write-Verbose "Property '$key' not found in the data"
                                    $false
                                }
                            }
                            "contains" { $_.$key -like "*$value*" }
                            "starts with" { $_.$key -like "$value*" }
                            "ends with" { $_.$key -like "*$value" }
                            default { throw "Unsupported comparison operator: $ComparisonOperator" }
                        }
                        Write-Debug "Condition met: $conditionMet"
                        
                        if ($LogicalOperator -eq 'OR') {
                            $result = $result -or $conditionMet
                            if ($result) { break }
                        }
                        else {
                            $result = $result -and $conditionMet
                        }
                    }
                    Write-Debug "Result for record: $result"
                    return $result
                }
            }
        }
        Write-Verbose "Finished Get-tdbRecord for table '$TableName'."
        Write-Debug "Final data: $($data | Out-String)"
        return $data
    }
    catch {
        $functionName = $MyInvocation.MyCommand.Name
        Update-ErrorHandling -ErrorRecord $_ -ErrorContext "$functionName function; Error reading records from '$TableName' '$tablePath'" -LogFilePath $config.LogFilePath  
    }
}

# Function to update records
function Update-tdbRecord {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]$TableName,
        [Parameter(Mandatory)]
        [hashtable]$Filter,
        [Parameter(Mandatory)]
        [hashtable]$NewValues
    )
    try {
        $tablePath = Join-Path -Path $config.DBDirectory -ChildPath "$TableName.csv"
        if (-not (Test-Path -Path $tablePath)) {
            Handle-tdbError -ErrorMessage "Table '$TableName' does not exist."
        }

        $data = Import-Csv -Path $tablePath
        $updated = $false

        foreach ($row in $data) {
            $match = $true
            foreach ($key in $Filter.Keys) {
                if ($row.$key -ne $Filter[$key]) {
                    $match = $false
                    break
                }
            }

            if ($match) {
                foreach ($key in $NewValues.Keys) {
                    $row.$key = $NewValues[$key]
                }
                $updated = $true
            }
        }

        if ($updated) {
            $data | Export-Csv -Path $tablePath -NoTypeInformation
            Write-tdbLog -Message "Updated records in '$TableName' where $Filter with $NewValues"
            return $true
        }
        else {
            Handle-tdbError -ErrorMessage "No matching records found to update in '$TableName'."
        }
    }
    catch {
        $functionName = $MyInvocation.MyCommand.Name
        Update-ErrorHandling -ErrorRecord $_ -ErrorContext "$functionName function; Error updating records in '$TableName'" -LogFilePath $config.LogFilePath  
    }
}

# Function to delete records
function Remove-tdbRecord {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]$TableName,
        [Parameter(Mandatory)]
        [hashtable]$Filter
    )
    try {
        $tablePath = Join-Path -Path $config.DBDirectory -ChildPath "$TableName.csv"
        if (-not (Test-Path -Path $tablePath)) {
            Handle-tdbError -ErrorMessage "Table '$TableName' does not exist."
        }

        $data = Import-Csv -Path $tablePath
        $originalCount = $data.Count

        $data = $data | Where-Object {
            $match = $true
            foreach ($key in $Filter.Keys) {
                if ($_.($key) -ne $Filter[$key]) {
                    $match = $false
                    break
                }
            }
            -not $match
        }

        if ($data.Count -lt $originalCount) {
            $data | Export-Csv -Path $tablePath -NoTypeInformation
            Write-tdbLog -Message "Deleted records from '$TableName' where $Filter"
            return $true
        }
        else {
            Handle-tdbError -ErrorMessage "No matching records found to delete in '$TableName'."
        }
    }
    catch {
        $functionName = $MyInvocation.MyCommand.Name
        Update-ErrorHandling -ErrorRecord $_ -ErrorContext "$functionName function; Error deleting records from '$TableName'" -LogFilePath $config.LogFilePath  
    }
}

# Function to create an index
function New-tdbIndex {
    param (
        [string]$TableName,
        [string]$ColumnName,
        [string]$IndexName
    )

    Write-Warning "The indexing feature is experimental and may not work as expected."

    # Ensure the index directory exists
    $indexDir = Join-Path -Path $config.DBDirectory -ChildPath "Indexes"
    if (-not (Test-Path -Path $indexDir)) {
        New-Item -Path $indexDir -ItemType Directory -Force
    }

    # Check if index already exists
    $indexPath = Join-Path -Path $indexDir -ChildPath "$IndexName.index"
    if (Test-Path $indexPath) {
        Handle-tdbError -ErrorMessage "Index '$IndexName' already exists."
    }

    # Create index file and write metadata
    New-Item -Path $indexPath -ItemType File -Force
    $metadata = @{
        TableName  = $TableName
        ColumnName = $ColumnName
        CreatedOn  = (Get-Date)
    }
    $metadata | Export-Clixml -Path $indexPath

    Write-tdbLog -Message "Index '$IndexName' created successfully."
}

# Function to list all indexes
function Get-tdbIndex {

    Write-Warning "The indexing feature is experimental and may not work as expected."

    $indexDir = Join-Path -Path $config.DBDirectory -ChildPath "Indexes"
    Get-ChildItem -Path $indexDir -Filter *.index | ForEach-Object {
        [PSCustomObject]@{
            IndexName = $_.BaseName
            Metadata  = Import-Clixml -Path $_.FullName
        }
    }
}

# Function to delete an index
function Remove-tdbIndex {
    param (
        [string]$IndexName
    )

    Write-Warning "The indexing feature is experimental and may not work as expected."

    $indexDir = Join-Path -Path $config.DBDirectory -ChildPath "Indexes"
    $indexPath = Join-Path -Path $indexDir -ChildPath "$IndexName.index"
    
    if (Test-Path $indexPath) {
        Remove-Item -Path $indexPath -Force
        Write-tdbLog -Message "Index '$IndexName' deleted successfully."
    }
    else {
        $functionName = $MyInvocation.MyCommand.Name
        Update-ErrorHandling -ErrorRecord $_ -ErrorContext "$functionName function; Index '$IndexName' does not exist." -LogFilePath $config.LogFilePath  
    }
}

function Reindex-tdbIndex {
    param (
        [Parameter(Mandatory = $true)]
        [string]$IndexName
    )

    Write-Warning "The indexing feature is experimental and may not work as expected."

    try {
        Write-Verbose "Starting reindexing process for index '$IndexName'."
        Write-Debug "Parameters: IndexName=$IndexName"

        $indexDir = Join-Path -Path $config.DBDirectory -ChildPath "Indexes"
        $indexPath = Join-Path -Path $indexDir -ChildPath "$IndexName.index"
        Write-Debug "Index path: $indexPath"
        
        if (-not (Test-Path $indexPath)) {
            Handle-tdbError -ErrorMessage "Index '$IndexName' does not exist."
            return
        }

        # Load existing metadata
        Write-Verbose "Loading existing metadata for index '$IndexName'."
        $metadata = Import-Clixml -Path $indexPath
        Write-Debug "Metadata loaded: $($metadata | Out-String)"

        # Validate metadata
        if (-not $metadata.TableName -or -not $metadata.ColumnName) {
            Handle-tdbError -ErrorMessage "Invalid metadata for index '$IndexName'."
            return
        }

        # Load table data
        $tablePath = Join-Path -Path $config.DBDirectory -ChildPath "$($metadata.TableName).csv"
        Write-Debug "Table path: $tablePath"
        if (-not (Test-Path $tablePath)) {
            Handle-tdbError -ErrorMessage "Table '$($metadata.TableName)' does not exist."
            return
        }
        Write-Verbose "Loading table data from '$tablePath'."
        $tableData = Import-Csv -Path $tablePath
        Write-Debug "Table data loaded: $($tableData | Out-String)"

        # Recreate index based on table data
        Write-Verbose "Recreating index based on table data."
        $indexData = [PSCustomObject]@{
            TableName  = $metadata.TableName
            ColumnName = $metadata.ColumnName
            CreatedOn  = (Get-Date)
            Records    = @()
        }

        foreach ($record in $tableData) {
            Write-Debug "Adding record to index: $($record | Out-String)"
            $indexData.Records += $record
        }

        # Save the new index
        Write-Verbose "Saving the new index to '$indexPath'."
        $indexData | Export-Clixml -Path $indexPath -Force

        Write-tdbLog -Message "Index '$IndexName' reindexed successfully based on table data."
        Write-Verbose "Reindexing process for index '$IndexName' completed successfully."
    }
    catch {
        $functionName = $MyInvocation.MyCommand.Name
        Update-ErrorHandling -ErrorRecord $_ -ErrorContext "$functionName function; Error reindexing '$IndexName'" -LogFilePath $config.LogFilePath  
    }
}


# Function to validate data types
function Validate-tdbDataType {
    param (
        [Parameter(Mandatory = $true)]
        [PSCustomObject] $Record,
        [Parameter(Mandatory = $true)]
        [hashtable] $Schema
    )
    foreach ($key in $Schema.Keys) {
        if ($Record.PSObject.Properties[$key]) {
            $expectedType = $Schema[$key]
            $actualType = $Record.$key.GetType().Name
            if ($actualType -ne $expectedType) {
                throw "Invalid data type for field '$key'. Expected: $expectedType, Found: $actualType"
            }
        }
    }
}

# Function to validate required fields
function Validate-tdbRequiredFields {
    param (
        [Parameter(Mandatory = $true)]
        [PSCustomObject] $Record,
        [Parameter(Mandatory = $true)]
        [string[]] $RequiredFields
    )
    foreach ($field in $RequiredFields) {
        if (-not $Record.PSObject.Properties[$field] -or [string]::IsNullOrEmpty($Record.$field)) {
            throw "Required field '$field' is missing or empty."
        }
    }
}

# Function to validate unique constraints
function Validate-tdbUniqueConstraints {
    param (
        [Parameter(Mandatory = $true)]
        [PSCustomObject] $Record,
        [Parameter(Mandatory = $true)]
        [string[]] $UniqueFields,
        [Parameter(Mandatory = $true)]
        [System.Collections.ArrayList] $ExistingRecords
    )
    foreach ($field in $UniqueFields) {
        foreach ($existingRecord in $ExistingRecords) {
            if ($existingRecord.$field -eq $Record.$field) {
                throw "Duplicate value found for unique field '$field'. Value: $($Record.$field)"
            }
        }
    }
}

# Sample usage guide
function Show-tdbUsage {
    Write-Output "Usage Guide for Miniature Text Relational DBMS (tdb)"
    Write-Output "---------------------------------------------"
    Write-Output "1. Creating a new table: New-tdbTable -TableName 'Users' -Columns @('Name', 'Email')"
    Write-Output "2. Inserting a record: Insert-tdbRecord -TableName 'Users' -Record @{Name='John Doe'; Email='john@example.com'}"
    Write-Output "3. Reading records: Get-tdbRecord -TableName 'Users' -Filter @{Name='John Doe'} -LogicalOperator equals"
    Write-Output "4. Getting table information: Get-tdbTable -TableName 'Users'"
    Write-Output "5. Updating records: Update-tdbRecord -TableName 'Users' -Filter @{ID=1} -NewValues @{Email='john.doe@example.com'}"
    Write-Output "6. Deleting records: Remove-tdbRecord -TableName 'Users' -Filter @{ID=1}"
    Write-Output "7. Troubleshooting Tips: Check the log file at `$config.LogFilePath for detailed error messages if any operation fails."
}
#endregion Functions


#region Entry Point
# Display the banner
Show-Banner
    
# Inform the user how to display the usage guide
Write-Output "To display the usage guide at any time, run the cmdlet: Show-tdbUsage"
    
try {
    # Configuration Section
    if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey('configFilePath') -and (Test-Path -Path $configFilePath -PathType Leaf)) {
        Write-Verbose "User provided configuration file path: $configFilePath. Loading configuration."
        # Load the configuration from the provided file
        $configFilePath = (Resolve-Path -Path $configFilePath).Path
        $config = Get-Content -Path $configFilePath | ConvertFrom-Json
        Write-Host "Configuration loaded successfully from user-provided path: $configFilePath"
    }
    else {
        # Determine the path to the default configuration file
        $Invocation = (Get-Variable MyInvocation).Value
        Write-Verbose "Determining the parent path of the current script."
        $parentPath = Split-Path -Parent $Invocation.MyCommand.Path
        Write-Verbose "Parent path determined: $parentPath"
        Write-Verbose "Joining the parent path with the default configuration file name."
        $ConfigFilePath = Join-Path -Path $parentPath -ChildPath ".tdb_default.config"
        Write-Host "Default configuration loaded successfully from path: $ConfigFilePath"
        
        if (-not (Test-Path -Path $ConfigFilePath -PathType Leaf)) {
            Write-Verbose "Default configuration file not found. Creating default configuration."
            # If the default config file does not exist, create a default configuration
            $config = @{
                LogFilePath    = "$env:USERPROFILE\Documents\$scriptname\Logs\tdb.Log"  # Path to the log file
                DBDirectory    = "$env:USERPROFILE\Documents\$scriptname\DB"            # Path to the database directory
                IndexDirectory = "$env:USERPROFILE\Documents\$scriptname\DB\Indexes"    # Path to the index directory
            }
            # Create default config file with default settings
            $config | ConvertTo-Json | Set-Content -Path $defaultConfigFilePath
            Write-Verbose "Default configuration file created at: $defaultConfigFilePath"
        }
        else {
            Write-Verbose "Default configuration file found. Loading configuration."
            # If the default config file exists, load the configuration from the file
            $config = Get-Content -Path $defaultConfigFilePath | ConvertFrom-Json
            Write-Verbose "Configuration loaded successfully from default path."
        }
    }
    # Check for script updates
    Get-CheckForScriptUpdate -currentScriptVersion $tdbVersion -scriptName $scriptname

}
catch [System.Exception] {
    # Handle any exceptions that occur during the configuration process
    $functionName = $MyInvocation.MyCommand.Name
    Update-ErrorHandling -ErrorRecord $_ -ErrorContext "$functionName function" -LogFilePath $config.LogFilePath  
}
#endregion Entry Point