// Inserts a row to the bottom of the table. func (table *Table) Insert(row map[string]string) int { // Seek to EOF _, err := table.DataFile.Seek(0, 2) if err == nil { // For the columns in their order for _, column := range table.ColumnsInOrder { value, exists := row[column.Name] if !exists { value = "" } // Keep writing the column value. status := table.Write(column, value) if status != st.OK { return status } } // Write a new-line character. _, err = table.DataFile.WriteString("\n") if err != nil { logg.Err("table", "Insert", err.Error()) return st.CannotWriteTableDataFile } } else { logg.Err("table", "Insert", err.Error()) return st.CannotSeekTableDataFile } return st.OK }
// Adds a new column. func (table *Table) Add(name string, length int) int { _, exists := table.Columns[name] if exists { return st.ColumnAlreadyExists } if len(name) > constant.MaxColumnNameLength { return st.ColumnNameTooLong } if length <= 0 { return st.InvalidColumnLength } var numberOfRows int numberOfRows, status := table.NumberOfRows() if status == st.OK && numberOfRows > 0 { // Rebuild data file if there are already rows in the table. // (To leave space for the new column) status = table.RebuildDataFile(name, length) table.pushNewColumn(name, length) } else { newColumn := table.pushNewColumn(name, length) // Write definition of the new column into definition file. _, err := table.DefFile.Seek(0, 2) if err != nil { logg.Err("table", "Add", err.Error()) return st.CannotSeekTableDefFile } _, err = table.DefFile.WriteString(column.ColumnToDef(newColumn)) if err != nil { logg.Err("table", "Add", err.Error()) return st.CannotWriteTableDefFile } } table.RowLength += length return st.OK }
// Creates a file and writes the content into it. func CreateAndWrite(filename, content string) int { file, err := os.Create(filename) defer file.Close() if err != nil { logg.Err("util", "CreateAndWrite", err) return st.CannotCreateFile } _, err = file.WriteString(content) if err != nil { logg.Err("util", "CreateAndWrite", err) return st.CannotCreateFile } return st.OK }
// Opens file handles. func (table *Table) OpenFiles() int { var err error table.DefFile, err = os.OpenFile(table.DefFilePath, os.O_RDWR, constant.DataFilePerm) if err == nil { table.DataFile, err = os.OpenFile(table.DataFilePath, os.O_RDWR, constant.DataFilePerm) if err != nil { logg.Err("table", "OpenFiles", err.Error()) return st.CannotOpenTableDataFile } } else { logg.Err("table", "OpenFiles", err.Error()) return st.CannotOpenTableDefFile } return st.OK }
// Renames table files. func Rename(path string, oldName string, newName string) int { for _, ext := range constant.TableFiles() { err := os.Rename(path+oldName+ext, path+newName+ext) if err != nil { logg.Err("tablefilemanager", "Rename", err) return st.CannotRenameTableFile } } for _, dir := range constant.TableDirs() { err := os.Rename(path+oldName+dir, path+newName+dir) if err != nil { logg.Err("tablefilemanager", "Rename", err) return st.CannotRenameTableDir } } return st.OK }
// Deletes table files func Delete(path string, name string) int { for _, ext := range constant.TableFiles() { err := os.Remove(path + name + ext) if err != nil { logg.Err("tablefilemanager", "Delete", err) return st.CannotRemoveTableFile } } for _, dir := range constant.TableDirs() { err := os.RemoveAll(path + name + dir) if err != nil { logg.Err("tablefilemanager", "Delete", err) return st.CannotRemoveTableDir } } return st.OK }
// Returns the number of rows in this table. func (table *Table) NumberOfRows() (int, int) { // var numberOfRows int // var dataFileInfo *os.FileInfo dataFileInfo, err := table.DataFile.Stat() if err != nil { logg.Err("table", "NumberOfRows", err.Error()) return 0, st.CannotStatTableDataFile } numberOfRows := int(dataFileInfo.Size()) / table.RowLength return numberOfRows, st.OK }
// Seeks to a row (e.g. row number 10). func (table *Table) Seek(rowNumber int) int { var numberOfRows int numberOfRows, status := table.NumberOfRows() if status == st.OK && rowNumber < numberOfRows { _, err := table.DataFile.Seek(int64(rowNumber*table.RowLength), 0) if err != nil { logg.Err("table", "Seek", err.Error()) return st.CannotSeekTableDataFile } } return st.OK }
// Constructs a Column from a column's text definition. func ColumnFromDef(offset int, definition string) (*Column, int) { var column *Column // Extract length and name from the definition. lengthName := strings.Split(definition, ":") length, err := strconv.Atoi(lengthName[1]) if err != nil { logg.Err("Column", "ColumnFromDef", "Definition malformed: "+definition) return nil, st.InvalidColumnDefinition } column = &Column{Offset: offset, Length: length, Name: lengthName[0]} return column, st.OK }
// Opens a table. func Open(path, name string) (*Table, int) { var table *Table table = new(Table) table.Path = path table.Name = name status := table.Init() if status != st.OK { logg.Err("table", "Open", "Failed to open"+path+name+" Err: "+string(status)) return nil, status } return table, st.OK }
// Creates table files. func Create(path string, name string) int { if len(name) > constant.MaxTableNameLength { return st.TableNameTooLong } // Create table files with extension names. for _, ext := range constant.TableFiles() { _, err := os.Create(path + name + ext) if err != nil { logg.Err("tablefilemanager", "Create", err) return st.CannotCreateTableFile } } // Create table directories with name suffixes. for _, dir := range constant.TableDirs() { err := os.Mkdir(path+name+dir, constant.TableDirPerm) if err != nil { logg.Err("tablefilemanager", "Create", err) return st.CannotCreateTableDir } } return st.OK }
// Opens a path as database. func Open(path string) (*Database, int) { var db *Database db = new(Database) db.Tables = make(map[string]*table.Table) // Open and read content of the path (as a directory). directory, err := os.Open(path) if err != nil { db = nil logg.Err("database", "Open", err.Error()) return db, st.CannotOpenDatabaseDirectory } defer directory.Close() fi, err := directory.Readdir(0) if err != nil { db = nil logg.Err("database", "Open", err.Error()) return db, st.CannotReadDatabaseDirectory } for _, fileInfo := range fi { // Extract extension of file name. if !fileInfo.IsDir() { name, ext := util.FilenameParts(fileInfo.Name()) // If extension is .data, open the file as a Table. if ext == "data" { _, exists := db.Tables[name] if !exists { var status int // Open the table and put it into tables map. db.Tables[name], status = table.Open(path, name) if status != st.OK { return nil, status } } } } } db.Path = path return db, db.PrepareForTriggers(false) }
// Flushes table's files func (table *Table) Flush() int { err := table.DefFile.Sync() if err == nil { err = table.DataFile.Sync() if err != nil { logg.Err("table", "Flush", err.Error()) return st.CannotFlushTableDataFile } } else { return st.CannotFlushTableDefFile } return st.OK }
// Removes a line's occurances from a file. func RemoveLine(filename, line string) int { // Open and read the file. file, err := os.Open(filename) if err != nil { logg.Err("util", "RemoveLine", err) return st.CannotReadFile } fi, err := file.Stat() if err != nil { logg.Err("util", "RemoveLine", err) return st.CannotReadFile } buffer := make([]byte, fi.Size()) _, err = file.Read(buffer) if err != nil { logg.Err("util", "RemoveLine", err) return st.CannotReadFile } file.Close() // Re-open the file and overwrite it. file, err = os.OpenFile(filename, os.O_WRONLY+os.O_TRUNC, 0666) defer file.Close() if err != nil { logg.Err("util", "RemoveLine", err) return st.CannotReadFile } lines := strings.Split(string(buffer), "\n") for _, content := range lines { if strings.TrimSpace(content) != strings.TrimSpace(line) { _, err = file.WriteString(content + "\n") if err != nil { logg.Err("util", "RemoveLine", err) return st.CannotWriteFile } } } return st.OK }
// Seeks to a row and column (e.g. row number 10 column "NAME"). func (table *Table) SeekColumn(rowNumber int, columnName string) int { status := table.Seek(rowNumber) if status == st.OK { column, exists := table.Columns[columnName] if exists { _, err := table.DataFile.Seek(int64(column.Offset), 1) if err != nil { logg.Err("table", "SeekColumn", err.Error()) return st.CannotSeekTableDataFile } } } return st.OK }
// Reads a row and return a map representation (name1:value1, name2:value2...) func (table *Table) Read(rowNumber int) (map[string]string, int) { row := make(map[string]string) status := table.Seek(rowNumber) if status == st.OK { rowInBytes := make([]byte, table.RowLength) _, err := table.DataFile.Read(rowInBytes) if err == nil { // For the columns in their order for _, column := range table.ColumnsInOrder { // column1:value2, column2:value2... row[column.Name] = strings.TrimSpace(string(rowInBytes[column.Offset : column.Offset+column.Length])) } } else { logg.Err("table", "Read", err.Error()) return nil, st.CannotReadTableDataFile } } return row, st.OK }
// Load the table (column definitions, etc.). func (table *Table) Init() int { // This function may be called multiple times, thus clear previous state. table.RowLength = 0 table.Columns = make(map[string]*column.Column) table.ColumnsInOrder = make([]*column.Column, 0) table.DefFilePath = table.Path + table.Name + ".def" table.DataFilePath = table.Path + table.Name + ".data" status := table.OpenFiles() if status != st.OK { return status } defFileInfo, err := table.DefFile.Stat() if err != nil { logg.Err("table", "Init", err.Error()) return st.CannotStatTableDefFile } // Read definition file into memeory. content := make([]byte, defFileInfo.Size()) table.DefFile.Read(content) // Each line contains one column definition. lines := strings.Split(string(content), "\n") for _, line := range lines { if line != "" { var aColumn *column.Column // Convert the definition into a Column. aColumn, status = column.ColumnFromDef(table.RowLength, line) if status != st.OK { return status } table.Columns[aColumn.Name] = aColumn table.ColumnsInOrder = append(table.ColumnsInOrder[:], aColumn) table.RowLength += aColumn.Length } } table.RowLength++ return st.OK }
// Returns existing shared and exclusive locks of a table. func LocksOf(t *table.Table) (*Locks, int) { // Read files in .shared directory. sharedLocksPath := t.Path + t.Name + ".shared" sharedLocksDir, err := os.Open(sharedLocksPath) if err != nil { return nil, st.CannotReadSharedLocksDir } defer sharedLocksDir.Close() fi, err := sharedLocksDir.Readdir(0) if err != nil { logg.Err("transaction", "LocksOf", err) return nil, st.CannotReadSharedLocksDir } locks := new(Locks) locks.Shared = make([]int64, 0) for _, fileInfo := range fi { // File name represents a transaction ID (also a timestamp). theID, err := strconv.ParseInt(fileInfo.Name(), 10, 64) // tt := time.Now().Nanosecond() + constant.LockTimeout if err != nil || theID > int64(time.Now().Nanosecond()+constant.LockTimeout) { // Remove expired shared lock. err = os.Remove(sharedLocksPath + "/" + fileInfo.Name()) logg.Warn("transaction", "LocksOf", "Expired shared lock ID "+ fileInfo.Name()+" file "+sharedLocksPath+"/"+fileInfo.Name()+" is removed") if err != nil { logg.Err("transaction", "LocksOf", err) return nil, st.CannotUnlockSharedLock } } else { locks.Shared = append(locks.Shared[:], theID) } } // Read the content of exclusive lock. exclusiveLockPath := t.Path + t.Name + ".exclusive" exclusiveFile, err := os.Open(exclusiveLockPath) if err != nil { return locks, st.OK } fi2, err := exclusiveFile.Stat() if err != nil { logg.Err("transaction", "LocksOf", err) return nil, st.CannotReadExclusiveLocksFile } // The file content is a transaction ID buffer := make([]byte, fi2.Size()) _, err = exclusiveFile.Read(buffer) if err != nil { logg.Err("transaction", "LocksOf", err) return nil, st.CannotReadExclusiveLocksFile } theID, err := strconv.ParseInt(string(buffer), 10, 64) if err != nil || theID > int64(time.Now().Nanosecond()+constant.LockTimeout) { // Remove expired exclusive lock. err = os.Remove(exclusiveLockPath) logg.Debug("transaction", "LocksOf", err) logg.Warn("transaction", "LocksOf", "Expired exclusive lock ID "+ string(buffer)+" file "+exclusiveLockPath+" is removed") if err != nil { logg.Err("transaction", "LocksOf", err) return nil, st.CannotUnlockExclusiveLock } } else { locks.Exclusive = theID } return locks, st.OK }