Installing libpq Library
- Install the PostgreSQL client library
libpq. - Ensure that the
libpq-fe.hheader file and related library files are correctly added to the development environment.
Configuring the Compiler
In Visual Studio, you need to set the project properties to include the PostgreSQL library files and header file paths.
- Additional Include Directories: Add the PostgreSQL include directory.
- Additional Library Directories: Add the PostgreSQL lib directory.
- Additional Dependencies: Add
libpq.lib.
Writing Code
- Include the necessary header files.
- Connect to the database.
- Execute SQL commands.
- Process the result set.
- Close the connection.
Below is a simple example demonstrating how to connect to a PostgreSQL database using C and execute a query:
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h> // PostgreSQL client library header file
int main() {
PGconn *conn; // PostgreSQL connection handle
PGresult *res; // Result set
const char *connection_string = "dbname=mydb user=myuser password=mypassword hostaddr=127.0.0.1 port=5432";
const char *sql = "SELECT * FROM mytable";
conn = PQconnectdb(connection_string); // Connect to the database
if (PQstatus(conn) != CONNECTION_OK) {
fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn));
PQfinish(conn);
return 1;
}
// Execute query
res = PQexec(conn, sql);
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
fprintf(stderr, "Query execution failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}
// Output results
int num_rows = PQntuples(res);
int num_cols = PQnfields(res);
for (int i = 0; i < num_rows; i++) {
for (int j = 0; j < num_cols; j++) {
printf("%s ", PQgetvalue(res, i, j));
}
printf("\n");
}
// Clean up resources
PQclear(res);
PQfinish(conn);
return 0;
}
Notes:
- Ensure the PostgreSQL service is running.
- Use the correct connection string.
- Handle error cases, such as connection failure or query failure.
- Ensure all resources are properly released.
Compiling and Running
When compiling, ensure to link the libpq library, for example using the gcc command:
gcc -o myapp myapp.c -lpq
If using Visual Studio, ensure the project properties are configured as described above.
Parameterized Queries
Using prepared statements can effectively prevent SQL injection attacks. Below is an example using a prepared statement:
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>
int main() {
PGconn *conn; // PostgreSQL connection handle
PGresult *res; // Result set
PGresult *stmt_res; // Prepared statement result set
const char *connection_string = "dbname=mydb user=myuser password=mypassword hostaddr=127.0.0.1 port=5432";
const char *sql = "PREPARE get_data(integer) AS SELECT * FROM mytable WHERE id = $1";
const char *execute_sql = "EXECUTE get_data(1)";
const char *deallocate_sql = "DEALLOCATE get_data";
conn = PQconnectdb(connection_string); // Connect to the database
if (PQstatus(conn) != CONNECTION_OK) {
fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn));
PQfinish(conn);
return 1;
}
// Prepare the prepared statement
stmt_res = PQexec(conn, sql);
if (PQresultStatus(stmt_res) != PGRES_COMMAND_OK) {
fprintf(stderr, "Preparing statement failed: %s", PQerrorMessage(conn));
PQclear(stmt_res);
PQfinish(conn);
return 1;
}
// Execute the prepared statement
res = PQexec(conn, execute_sql);
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
fprintf(stderr, "Executing prepared statement failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}
// Output results
int num_rows = PQntuples(res);
int num_cols = PQnfields(res);
for (int i = 0; i < num_rows; i++) {
for (int j = 0; j < num_cols; j++) {
printf("%s ", PQgetvalue(res, i, j));
}
printf("\n");
}
// Clean up resources
PQclear(res);
PQclear(stmt_res);
// Deallocate the prepared statement
stmt_res = PQexec(conn, deallocate_sql);
if (PQresultStatus(stmt_res) != PGRES_COMMAND_OK) {
fprintf(stderr, "Deallocating statement failed: %s", PQerrorMessage(conn));
PQclear(stmt_res);
PQfinish(conn);
return 1;
}
PQclear(stmt_res);
PQfinish(conn);
return 0;
}
Transaction Management
Transaction management ensures that a group of operations either all succeed or all fail. Below is an example using transactions:
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>
int main() {
PGconn *conn; // PostgreSQL connection handle
PGresult *res; // Result set
const char *connection_string = "dbname=mydb user=myuser password=mypassword hostaddr=127.0.0.1 port=5432";
conn = PQconnectdb(connection_string); // Connect to the database
if (PQstatus(conn) != CONNECTION_OK) {
fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn));
PQfinish(conn);
return 1;
}
// Start transaction
res = PQexec(conn, "BEGIN");
if (PQresultStatus(res) != PGRES_COMMAND_OK) {
fprintf(stderr, "Starting transaction failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}
// Insert data
const char *insert_sql = "INSERT INTO mytable (column1, column2) VALUES ('value1', 'value2')";
res = PQexec(conn, insert_sql);
if (PQresultStatus(res) != PGRES_COMMAND_OK) {
fprintf(stderr, "Inserting data failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}
// Update data
const char *update_sql = "UPDATE mytable SET column1 = 'new_value' WHERE id = 1";
res = PQexec(conn, update_sql);
if (PQresultStatus(res) != PGRES_COMMAND_OK) {
fprintf(stderr, "Updating data failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}
// Commit transaction
res = PQexec(conn, "COMMIT");
if (PQresultStatus(res) != PGRES_COMMAND_OK) {
fprintf(stderr, "Committing transaction failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}
PQclear(res);
PQfinish(conn);
return 0;
}
Error Handling
Enhancing the error handling mechanism ensures that the program provides clear feedback when issues occur and handles errors gracefully. Below is an improved error handling example:
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>
void handle_pq_error(PGconn *conn, const char *message) {
fprintf(stderr, "%s: %s", message, PQerrorMessage(conn));
PQfinish(conn);
exit(1);
}
int main() {
PGconn *conn; // PostgreSQL connection handle
PGresult *res; // Result set
const char *connection_string = "dbname=mydb user=myuser password=mypassword hostaddr=127.0.0.1 port=5432";
const char *sql = "SELECT * FROM mytable";
conn = PQconnectdb(connection_string); // Connect to the database
if (PQstatus(conn) != CONNECTION_OK) {
handle_pq_error(conn, "Connection to database failed");
}
// Execute query
res = PQexec(conn, sql);
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
handle_pq_error(conn, "Query execution failed");
}
// Output results
int num_rows = PQntuples(res);
int num_cols = PQnfields(res);
for (int i = 0; i < num_rows; i++) {
for (int j = 0; j < num_cols; j++) {
printf("%s ", PQgetvalue(res, i, j));
}
printf("\n");
}
// Clean up resources
PQclear(res);
PQfinish(conn);
return 0;
}
Batch Inserting Data
When inserting large amounts of data, using batch inserts can significantly improve efficiency. Below is an example using batch insertion:
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>
int main() {
PGconn *conn; // PostgreSQL connection handle
PGresult *res; // Result set
const char *connection_string = "dbname=mydb user=myuser password=mypassword hostaddr=127.0.0.1 port=5432";
conn = PQconnectdb(connection_string); // Connect to the database
if (PQstatus(conn) != CONNECTION_OK) {
fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn));
PQfinish(conn);
return 1;
}
// Create table
const char *create_table_sql = "CREATE TABLE IF NOT EXISTS mytable (id SERIAL PRIMARY KEY, name TEXT)";
res = PQexec(conn, create_table_sql);
if (PQresultStatus(res) != PGRES_COMMAND_OK) {
fprintf(stderr, "Creating table failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}
// Start transaction
res = PQexec(conn, "BEGIN");
if (PQresultStatus(res) != PGRES_COMMAND_OK) {
fprintf(stderr, "Starting transaction failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}
// Batch insert data
const char *batch_insert_sql = "INSERT INTO mytable (name) VALUES ";
for (int i = 0; i < 1000; i++) {
if (i > 0) {
batch_insert_sql = strcat(batch_insert_sql, ", ");
}
batch_insert_sql = strcat(batch_insert_sql, "('data'");
if (i == 999) {
batch_insert_sql = strcat(batch_insert_sql, ")");
} else {
batch_insert_sql = strcat(batch_insert_sql, "), ");
}
}
res = PQexec(conn, batch_insert_sql);
if (PQresultStatus(res) != PGRES_COMMAND_OK) {
fprintf(stderr, "Batch insert failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}
// Commit transaction
res = PQexec(conn, "COMMIT");
if (PQresultStatus(res) != PGRES_COMMAND_OK) {
fprintf(stderr, "Committing transaction failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}
PQclear(res);
PQfinish(conn);
return 0;
}
Asynchronous Queries
Using asynchronous queries allows other tasks to be performed while waiting for the database response, thereby improving application responsiveness. Below is an example using asynchronous queries:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libpq-fe.h>
int main() {
PGconn *conn; // PostgreSQL connection handle
PGresult *res; // Result set
const char *connection_string = "dbname=mydb user=myuser password=mypassword hostaddr=127.0.0.1 port=5432";
const char *sql = "SELECT * FROM mytable";
conn = PQconnectdb(connection_string); // Connect to the database
if (PQstatus(conn) != CONNECTION_OK) {
fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn));
PQfinish(conn);
return 1;
}
// Send asynchronous query
if (!PQsendQuery(conn, sql)) {
fprintf(stderr, "Sending query failed: %s", PQerrorMessage(conn));
PQfinish(conn);
return 1;
}
// Wait for query to complete
while (PQisBusy(conn)) {
sleep(1); // Wait one second
}
// Get result
res = PQgetResult(conn);
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
fprintf(stderr, "Query execution failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}
// Output results
int num_rows = PQntuples(res);
int num_cols = PQnfields(res);
for (int i = 0; i < num_rows; i++) {
for (int j = 0; j < num_cols; j++) {
printf("%s ", PQgetvalue(res, i, j));
}
printf("\n");
}
// Clean up resources
PQclear(res);
PQfinish(conn);
return 0;
}
Using JSON Type
PostgreSQL supports the JSON type, which can be used to store structured data. Below is an example using the JSON type:
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>
int main() {
PGconn *conn; // PostgreSQL connection handle
PGresult *res; // Result set
const char *connection_string = "dbname=mydb user=myuser password=mypassword hostaddr=127.0.0.1 port=5432";
const char *json_data = "{\"key\": \"value\"}";
conn = PQconnectdb(connection_string); // Connect to the database
if (PQstatus(conn) != CONNECTION_OK) {
fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn));
PQfinish(conn);
return 1;
}
// Create table
const char *create_table_sql = "CREATE TABLE IF NOT EXISTS mytable (id SERIAL PRIMARY KEY, data JSON)";
res = PQexec(conn, create_table_sql);
if (PQresultStatus(res) != PGRES_COMMAND_OK) {
fprintf(stderr, "Creating table failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}
// Insert JSON data
const char *insert_sql = "INSERT INTO mytable (data) VALUES ($1)";
res = PQexecParams(conn, insert_sql, 1, NULL, &json_data, NULL, NULL, 0);
if (PQresultStatus(res) != PGRES_COMMAND_OK) {
fprintf(stderr, "Inserting JSON data failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}
// Query JSON data
const char *query_sql = "SELECT data FROM mytable";
res = PQexec(conn, query_sql);
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
fprintf(stderr, "Querying JSON data failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}
// Output results
int num_rows = PQntuples(res);
int num_cols = PQnfields(res);
for (int i = 0; i < num_rows; i++) {
for (int j = 0; j < num_cols; j++) {
printf("%s ", PQgetvalue(res, i, j));
}
printf("\n");
}
// Clean up resources
PQclear(res);
PQfinish(conn);
return 0;
}



