package countersql import ( "database/sql" "fmt" ) // Configuration holds the DSN connection string and a resource Semaphore to limit the number of active connections // Note: This is a copied design pattern I've used for other projects which had much more data inside a Configuration struct. // Examples include semaphores and persistent connection pools, which I've stripped out for this project. type Configuration struct { DSN string } // Connection represents a single connection to the database, however there may be many instances / connections // Note: This is a copied design pattern I've used for other projects which had much more data inside a Connection struct. type Connection struct { DB *sql.DB } // HasIPVisited returns true only if the IP address is present in the database func (conn Connection) HasIPVisited(ipAddress string) (bool, error) { rows, err := conn.DB.Query(`SELECT id FROM visit WHERE ip_address = ? LIMIT 1;`, ipAddress) if err != nil { return false, fmt.Errorf("SELECT query failed: %v", err) } defer rows.Close() if !rows.Next() { return false, nil } return true, nil } // GetUniqueVisits counts the number of entires in the visits table, representing one unique source IP address per row func (conn Connection) GetUniqueVisits() (int, error) { rows, err := conn.DB.Query(`SELECT COUNT(*) FROM visit`) if err != nil { return 0, fmt.Errorf("SELECT query failed: %v", err) } defer rows.Close() if !rows.Next() { return 0, nil } var uniqueVists int if err := rows.Scan(&uniqueVists); err != nil { return 0, fmt.Errorf("failed to scan database row: %v", err) } return uniqueVists, nil } // IncrementVisitor accepts an IP address and updates the row matching that IP address // It does not check if the row matching the IP address supplied exists or not func (conn Connection) IncrementVisitor(ipAddress string) error { _, err := conn.DB.Exec(`UPDATE visit SET visits = visits + 1, last_visited = NOW() WHERE ip_address = ?`, ipAddress) if err != nil { return fmt.Errorf("UPDATE query failed: %v", err) } return nil } // AddVisitor accepts an IP address and inserts a new row into the database as this represents a new unique visitor func (conn Connection) AddVisitor(ipAddress string) error { _, err := conn.DB.Exec(`INSERT INTO visit (ip_address, visits, last_visited) VALUES (?, '0', NOW())`, ipAddress) if err != nil { return fmt.Errorf("INSERT query failed: %v", err) } return nil } // Connect will open a TCP connection to the database with the given DSN configuration func (conf Configuration) Connect() (*Connection, error) { conn := &Connection{} var err error conn.DB, err = sql.Open("mysql", conf.DSN) if err != nil { return nil, fmt.Errorf("failed to open db: %v", err) } return conn, nil }