2024/11/21

GNU Libmicrohttpd

GNU Libmicrohttpd 是一個函式庫,授權為 GNU LGPL v2.1 (使用者也可以選擇 LGPL v2.1 之後的版本), 最主要的功能是內嵌 HTTP server 到應用程式內,有多種 threading mode 可以選擇, 支援 HTTP/1.1 與 IPv6,並且透過 GnuTLS 支援 SSL/TLS 協定。 因此,當應用程式需要內嵌 web server 作為簡單的 web 介面時,可以考慮使用 GNU Libmicrohttpd。

在 openSUSE Tumbleweed 安裝相關的開發檔案:

sudo zypper in libmicrohttpd-devel

下面是首頁上的範例:

#include <microhttpd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PAGE                                                                   \
    "<html><head><title>libmicrohttpd demo</title>"                            \
    "</head><body>libmicrohttpd demo</body></html>"

static enum MHD_Result ahc_echo(void *cls, struct MHD_Connection *connection,
                                const char *url, const char *method,
                                const char *version, const char *upload_data,
                                size_t *upload_data_size, void **ptr) {
    static int dummy;
    const char *page = cls;
    struct MHD_Response *response;
    int ret;

    if (0 != strcmp(method, "GET"))
        return MHD_NO; /* unexpected method */
    if (&dummy != *ptr) {
        /* The first time only the headers are valid,
           do not respond in the first round... */
        *ptr = &dummy;
        return MHD_YES;
    }

    if (0 != *upload_data_size)
        return MHD_NO; /* upload data in a GET!? */
        
    *ptr = NULL;       /* clear context pointer */
    response = MHD_create_response_from_buffer(strlen(page), (void *)page,
                                               MHD_RESPMEM_PERSISTENT);
    ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
    MHD_destroy_response(response);

    return ret;
}

int main(int argc, char **argv) {
    struct MHD_Daemon *d;

    if (argc != 2) {
        printf("%s PORT\n", argv[0]);
        return 1;
    }

    d = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, atoi(argv[1]), NULL,
                         NULL, &ahc_echo, PAGE, MHD_OPTION_END);
    if (d == NULL)
        return 1;

    (void)getc(stdin);
    MHD_stop_daemon(d);
    return 0;
}

下面是加入 SSL/TLS 以及靜態檔案的支援:

#include <microhttpd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/stat.h>

#define BUF_SIZE 1024
#define MAX_URL_LEN 255

#define EMPTY_PAGE                                                             \
    "<html><head><title>File not found</title></head><body>File not "          \
    "found</body></html>"

/* test server key */
static const char key_pem[] =
  "-----BEGIN PRIVATE KEY-----\n\
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCff7amw9zNSE+h\n\
rOMhBrzbbsJluUP3gmd8nOKY5MUimoPkxmAXfp2L0il+MPZT/ZEmo11q0k6J2jfG\n\
UBQ+oZW9ahNZ9gCDjbYlBblo/mqTai+LdeLO3qk53d0zrZKXvCO6sA3uKpG2WR+g\n\
+sNKxfYpIHCpanqBU6O+degIV/+WKy3nQ2Fwp7K5HUNj1u0pg0QQ18yf68LTnKFU\n\
HFjZmmaaopWki5wKSBieHivzQy6w+04HSTogHHRK/y/UcoJNSG7xnHmoPPo1vLT8\n\
CMRIYnSSgU3wJ43XBJ80WxrC2dcoZjV2XZz+XdQwCD4ZrC1ihykcAmiQA+sauNm7\n\
dztOMkGzAgMBAAECggEAIbKDzlvXDG/YkxnJqrKXt+yAmak4mNQuNP+YSCEdHSBz\n\
+SOILa6MbnvqVETX5grOXdFp7SWdfjZiTj2g6VKOJkSA7iKxHRoVf2DkOTB3J8np\n\
XZd8YaRdMGKVV1O2guQ20Dxd1RGdU18k9YfFNsj4Jtw5sTFTzHr1P0n9ybV9xCXp\n\
znSxVfRg8U6TcMHoRDJR9EMKQMO4W3OQEmreEPoGt2/+kMuiHjclxLtbwDxKXTLP\n\
pD0gdg3ibvlufk/ccKl/yAglDmd0dfW22oS7NgvRKUve7tzDxY1Q6O5v8BCnLFSW\n\
D+z4hS1PzooYRXRkM0xYudvPkryPyu+1kEpw3fNsoQKBgQDRfXJo82XQvlX8WPdZ\n\
Ts3PfBKKMVu3Wf8J3SYpuvYT816qR3ot6e4Ivv5ZCQkdDwzzBKe2jAv6JddMJIhx\n\
pkGHc0KKOodd9HoBewOd8Td++hapJAGaGblhL5beIidLKjXDjLqtgoHRGlv5Cojo\n\
zHa7Viel1eOPPcBumhp83oJ+mQKBgQDC6PmdETZdrW3QPm7ZXxRzF1vvpC55wmPg\n\
pRfTRM059jzRzAk0QiBgVp3yk2a6Ob3mB2MLfQVDgzGf37h2oO07s5nspSFZTFnM\n\
KgSjFy0xVOAVDLe+0VpbmLp1YUTYvdCNowaoTE7++5rpePUDu3BjAifx07/yaSB+\n\
W+YPOfOuKwKBgQCGK6g5G5qcJSuBIaHZ6yTZvIdLRu2M8vDral5k3793a6m3uWvB\n\
OFAh/eF9ONJDcD5E7zhTLEMHhXDs7YEN+QODMwjs6yuDu27gv97DK5j1lEsrLUpx\n\
XgRjAE3KG2m7NF+WzO1K74khWZaKXHrvTvTEaxudlO3X8h7rN3u7ee9uEQKBgQC2\n\
wI1zeTUZhsiFTlTPWfgppchdHPs6zUqq0wFQ5Zzr8Pa72+zxY+NJkU2NqinTCNsG\n\
ePykQ/gQgk2gUrt595AYv2De40IuoYk9BlTMuql0LNniwsbykwd/BOgnsSlFdEy8\n\
0RQn70zOhgmNSg2qDzDklJvxghLi7zE5aV9//V1/ewKBgFRHHZN1a8q/v8AAOeoB\n\
ROuXfgDDpxNNUKbzLL5MO5odgZGi61PBZlxffrSOqyZoJkzawXycNtoBP47tcVzT\n\
QPq5ZOB3kjHTcN7dRLmPWjji9h4O3eHCX67XaPVMSWiMuNtOZIg2an06+jxGFhLE\n\
qdJNJ1DkyUc9dN2cliX4R+rG\n\
-----END PRIVATE KEY-----";

/* test server CA signed certificates */
static const char cert_pem[] =
  "-----BEGIN CERTIFICATE-----\n\
MIIFSzCCAzOgAwIBAgIBBDANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCUlUx\n\
DzANBgNVBAgMBk1vc2NvdzEPMA0GA1UEBwwGTW9zY293MRswGQYDVQQKDBJ0ZXN0\n\
LWxpYm1pY3JvaHR0cGQxITAfBgkqhkiG9w0BCQEWEm5vYm9keUBleGFtcGxlLm9y\n\
ZzEQMA4GA1UEAwwHdGVzdC1DQTAgFw0yMjA0MjAxODQzMDJaGA8yMTIyMDMyNjE4\n\
NDMwMlowZTELMAkGA1UEBhMCUlUxDzANBgNVBAgMBk1vc2NvdzEPMA0GA1UEBwwG\n\
TW9zY293MRswGQYDVQQKDBJ0ZXN0LWxpYm1pY3JvaHR0cGQxFzAVBgNVBAMMDnRl\n\
c3QtbWhkc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn3+2\n\
psPczUhPoazjIQa8227CZblD94JnfJzimOTFIpqD5MZgF36di9IpfjD2U/2RJqNd\n\
atJOido3xlAUPqGVvWoTWfYAg422JQW5aP5qk2ovi3Xizt6pOd3dM62Sl7wjurAN\n\
7iqRtlkfoPrDSsX2KSBwqWp6gVOjvnXoCFf/list50NhcKeyuR1DY9btKYNEENfM\n\
n+vC05yhVBxY2ZpmmqKVpIucCkgYnh4r80MusPtOB0k6IBx0Sv8v1HKCTUhu8Zx5\n\
qDz6Nby0/AjESGJ0koFN8CeN1wSfNFsawtnXKGY1dl2c/l3UMAg+GawtYocpHAJo\n\
kAPrGrjZu3c7TjJBswIDAQABo4HmMIHjMAsGA1UdDwQEAwIFoDAMBgNVHRMBAf8E\n\
AjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMBMDEGA1UdEQQqMCiCDnRlc3QtbWhk\n\
c2VydmVyhwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMB0GA1UdDgQWBBQ57Z06WJae\n\
8fJIHId4QGx/HsRgDDAoBglghkgBhvhCAQ0EGxYZVGVzdCBsaWJtaWNyb2h0dHBk\n\
IHNlcnZlcjARBglghkgBhvhCAQEEBAMCBkAwHwYDVR0jBBgwFoAUWHVDwKVqMcOF\n\
Nd0arI3/QB3W6SwwDQYJKoZIhvcNAQELBQADggIBAI7Lggm/XzpugV93H5+KV48x\n\
X+Ct8unNmPCSzCaI5hAHGeBBJpvD0KME5oiJ5p2wfCtK5Dt9zzf0S0xYdRKqU8+N\n\
aKIvPoU1hFixXLwTte1qOp6TviGvA9Xn2Fc4n36dLt6e9aiqDnqPbJgBwcVO82ll\n\
HJxVr3WbrAcQTB3irFUMqgAke/Cva9Bw79VZgX4ghb5EnejDzuyup4pHGzV10Myv\n\
hdg+VWZbAxpCe0S4eKmstZC7mWsFCLeoRTf/9Pk1kQ6+azbTuV/9QOBNfFi8QNyb\n\
18jUjmm8sc2HKo8miCGqb2sFqaGD918hfkWmR+fFkzQ3DZQrT+eYbKq2un3k0pMy\n\
UySy8SRn1eadfab+GwBVb68I9TrPRMrJsIzysNXMX4iKYl2fFE/RSNnaHtPw0C8y\n\
B7memyxPRl+H2xg6UjpoKYh3+8e44/XKm0rNIzXjrwA8f8gnw2TbqmMDkj1YqGnC\n\
SCj5A27zUzaf2pT/YsnQXIWOJjVvbEI+YKj34wKWyTrXA093y8YI8T3mal7Kr9YM\n\
WiIyPts0/aVeziM0Gunglz+8Rj1VesL52FTurobqusPgM/AME82+qb/qnxuPaCKj\n\
OT1qAbIblaRuWqCsid8BzP7ZQiAnAWgMRSUg1gzDwSwRhrYQRRWAyn/Qipzec+27\n\
/w0gW9EVWzFhsFeGEssi\n\
-----END CERTIFICATE-----";

static ssize_t file_reader(void *cls, uint64_t pos, char *buf, size_t max) {
    FILE *file = (FILE *)cls;
    size_t bytes_read;

    /* 'fseek' may not support files larger 2GiB, depending on platform.
     * For production code, make sure that 'pos' has valid values, supported by
     * 'fseek', or use 'fseeko' or similar function. */
    if (0 != fseek(file, (long)pos, SEEK_SET))
        return MHD_CONTENT_READER_END_WITH_ERROR;
    bytes_read = fread(buf, 1, max, file);
    if (0 == bytes_read)
        return (0 != ferror(file)) ? MHD_CONTENT_READER_END_WITH_ERROR
                                   : MHD_CONTENT_READER_END_OF_STREAM;
    return (ssize_t)bytes_read;
}

static void file_free_callback(void *cls) {
    FILE *file = cls;
    fclose(file);
}

/* HTTP access handler call back */
static enum MHD_Result http_ahc(void *cls, struct MHD_Connection *connection,
                                const char *url, const char *method,
                                const char *version, const char *upload_data,
                                size_t *upload_data_size, void **req_cls) {
    static int aptr;
    struct MHD_Response *response;
    enum MHD_Result ret;
    FILE *file;
    int fd;
    struct stat buf;
    (void)cls;              /* Unused. Silent compiler warning. */
    (void)version;          /* Unused. Silent compiler warning. */
    (void)upload_data;      /* Unused. Silent compiler warning. */
    (void)upload_data_size; /* Unused. Silent compiler warning. */

    if (0 != strcmp(method, MHD_HTTP_METHOD_GET))
        return MHD_NO; /* unexpected method */
    if (&aptr != *req_cls) {
        /* do never respond on first call */
        *req_cls = &aptr;
        return MHD_YES;
    }
    *req_cls = NULL; /* reset when done */

    file = fopen(&url[1], "rb");
    if (NULL != file) {
        fd = fileno(file);
        if (-1 == fd) {
            (void)fclose(file);
            return MHD_NO; /* internal error */
        }
        if ((0 != fstat(fd, &buf)) || (!S_ISREG(buf.st_mode))) {
            /* not a regular file, refuse to serve */
            fclose(file);
            file = NULL;
        }
    }

    if (NULL == file) {
        response = MHD_create_response_from_buffer_static(
            strlen(EMPTY_PAGE), (const void *)EMPTY_PAGE);
        ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
        MHD_destroy_response(response);
    } else {
        response = MHD_create_response_from_callback(
            (size_t)buf.st_size, 32 * 1024, /* 32k page size */
            &file_reader, file, &file_free_callback);
        if (NULL == response) {
            fclose(file);
            return MHD_NO;
        }
        ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
        MHD_destroy_response(response);
    }
    return ret;
}

int main(int argc, char *const *argv) {
    struct MHD_Daemon *TLS_daemon;
    int port;

    if (argc != 2) {
        printf("%s PORT\n", argv[0]);
        return 1;
    }

    port = atoi(argv[1]);
    if ((1 > port) || (port > 65535)) {
        fprintf(stderr, "Port must be a number between 1 and 65535.\n");
        return 1;
    }

    TLS_daemon = MHD_start_daemon(
        MHD_USE_THREAD_PER_CONNECTION | MHD_USE_INTERNAL_POLLING_THREAD |
            MHD_USE_ERROR_LOG | MHD_USE_TLS,
        (uint16_t)port, NULL, NULL, &http_ahc, NULL,
        MHD_OPTION_CONNECTION_TIMEOUT, 256, MHD_OPTION_HTTPS_MEM_KEY, key_pem,
        MHD_OPTION_HTTPS_MEM_CERT, cert_pem, MHD_OPTION_END);
    if (NULL == TLS_daemon) {
        fprintf(stderr, "Error: failed to start TLS_daemon.\n");
        return 1;
    }
    printf("MHD daemon listening on port %u\n", (unsigned int)port);

    (void)getc(stdin);
    MHD_stop_daemon(TLS_daemon);
    return 0;
}

接下來使用自簽憑證設定 HTTPS 並且採用 GnuTLS 載入檔案的方式。
首先建立 ssl.conf 設定檔:

[req]
prompt = no
default_md = sha256
default_bits = 2048
distinguished_name = dn
x509_extensions = v3_req

[dn]
C = TW
ST = Taiwan
L = Taipei
O = Orange Inc.
OU = IT Department
emailAddress = admin@example.com
CN = localhost

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = *.localhost
DNS.2 = localhost
IP.1 = 127.0.0.1

透過 OpenSSL 指令建立開發測試用途的自簽憑證:

openssl req -x509 -new -nodes -sha256 -utf8 -days 3650 \
-newkey rsa:2048 -keyout httpd.key -out httpd.crt -config ssl.conf

下面是一個使用的例子:

#include <microhttpd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/stat.h>

#define BUF_SIZE 1024
#define MAX_URL_LEN 255

#define EMPTY_PAGE                                                             \
    "<html><head><title>File not found</title></head><body>File not "          \
    "found</body></html>"

#include <gnutls/gnutls.h>
#include <gnutls/abstract.h>

/**
 * A hostname, server key and certificate.
 */
struct Hosts {
    struct Hosts *next;
    const char *hostname;
    gnutls_pcert_st pcrt;
    gnutls_privkey_t key;
};

#define PAGE                                                                   \
    "<html><head><title>libmicrohttpd https demo</title>"                      \
    "</head><body>libmicrohttpd https demo</body></html>"

static struct Hosts *hosts;

/* Load the certificate and the private key.
 * (This code is largely taken from GnuTLS).
 */
static void load_keys(const char *hostname, const char *CERT_FILE,
                      const char *KEY_FILE) {
    int ret;
    gnutls_datum_t data;
    struct Hosts *host;

    host = malloc(sizeof(struct Hosts));
    if (NULL == host)
        abort();
    host->hostname = hostname;
    host->next = hosts;
    hosts = host;

    ret = gnutls_load_file(CERT_FILE, &data);
    if (ret < 0) {
        fprintf(stderr, "*** Error loading certificate file %s.\n", CERT_FILE);
        exit(1);
    }
    ret = gnutls_pcert_import_x509_raw(&host->pcrt, &data, GNUTLS_X509_FMT_PEM,
                                       0);
    if (ret < 0) {
        fprintf(stderr, "*** Error loading certificate file: %s\n",
                gnutls_strerror(ret));
        exit(1);
    }
    gnutls_free(data.data);

    ret = gnutls_load_file(KEY_FILE, &data);
    if (ret < 0) {
        fprintf(stderr, "*** Error loading key file %s.\n", KEY_FILE);
        exit(1);
    }

    gnutls_privkey_init(&host->key);
    ret = gnutls_privkey_import_x509_raw(host->key, &data, GNUTLS_X509_FMT_PEM,
                                         NULL, 0);
    if (ret < 0) {
        fprintf(stderr, "*** Error loading key file: %s\n",
                gnutls_strerror(ret));
        exit(1);
    }
    gnutls_free(data.data);
}

static int cert_callback(gnutls_session_t session,
                         const gnutls_datum_t *req_ca_dn, int nreqs,
                         const gnutls_pk_algorithm_t *pk_algos,
                         int pk_algos_length, gnutls_pcert_st **pcert,
                         unsigned int *pcert_length, gnutls_privkey_t *pkey) {
    char name[256];
    size_t name_len;
    struct Hosts *host;
    unsigned int type;
    (void)req_ca_dn;
    (void)nreqs;
    (void)pk_algos;
    (void)pk_algos_length; /* Unused. Silent compiler warning. */

    name_len = sizeof(name);
    if (GNUTLS_E_SUCCESS !=
        gnutls_server_name_get(session, name, &name_len, &type, 0 /* index */))
        return -1;
    for (host = hosts; NULL != host; host = host->next)
        if (0 == strncmp(name, host->hostname, name_len))
            break;
    if (NULL == host) {
        fprintf(stderr, "Need certificate for %.*s\n", (int)name_len, name);
        return -1;
    }
#if 1
    fprintf(stderr, "Returning certificate for %.*s\n", (int)name_len, name);
#endif
    *pkey = host->key;
    *pcert_length = 1;
    *pcert = &host->pcrt;
    return 0;
}

static ssize_t file_reader(void *cls, uint64_t pos, char *buf, size_t max) {
    FILE *file = (FILE *)cls;
    size_t bytes_read;

    /* 'fseek' may not support files larger 2GiB, depending on platform.
     * For production code, make sure that 'pos' has valid values, supported by
     * 'fseek', or use 'fseeko' or similar function. */
    if (0 != fseek(file, (long)pos, SEEK_SET))
        return MHD_CONTENT_READER_END_WITH_ERROR;
    bytes_read = fread(buf, 1, max, file);
    if (0 == bytes_read)
        return (0 != ferror(file)) ? MHD_CONTENT_READER_END_WITH_ERROR
                                   : MHD_CONTENT_READER_END_OF_STREAM;
    return (ssize_t)bytes_read;
}

static void file_free_callback(void *cls) {
    FILE *file = cls;
    fclose(file);
}

/* HTTP access handler call back */
static enum MHD_Result http_ahc(void *cls, struct MHD_Connection *connection,
                                const char *url, const char *method,
                                const char *version, const char *upload_data,
                                size_t *upload_data_size, void **req_cls) {
    static int aptr;
    struct MHD_Response *response;
    enum MHD_Result ret;
    FILE *file;
    int fd;
    struct stat buf;
    (void)cls;              /* Unused. Silent compiler warning. */
    (void)version;          /* Unused. Silent compiler warning. */
    (void)upload_data;      /* Unused. Silent compiler warning. */
    (void)upload_data_size; /* Unused. Silent compiler warning. */

    if (0 != strcmp(method, MHD_HTTP_METHOD_GET))
        return MHD_NO; /* unexpected method */
    if (&aptr != *req_cls) {
        /* do never respond on first call */
        *req_cls = &aptr;
        return MHD_YES;
    }
    *req_cls = NULL; /* reset when done */

    file = fopen(&url[1], "rb");
    if (NULL != file) {
        fd = fileno(file);
        if (-1 == fd) {
            (void)fclose(file);
            return MHD_NO; /* internal error */
        }
        if ((0 != fstat(fd, &buf)) || (!S_ISREG(buf.st_mode))) {
            /* not a regular file, refuse to serve */
            fclose(file);
            file = NULL;
        }
    }

    if (NULL == file) {
        response = MHD_create_response_from_buffer_static(
            strlen(EMPTY_PAGE), (const void *)EMPTY_PAGE);
        ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
        MHD_destroy_response(response);
    } else {
        response = MHD_create_response_from_callback(
            (size_t)buf.st_size, 32 * 1024, /* 32k page size */
            &file_reader, file, &file_free_callback);
        if (NULL == response) {
            fclose(file);
            return MHD_NO;
        }
        ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
        MHD_destroy_response(response);
    }
    return ret;
}

int main(int argc, char *const *argv) {
    struct MHD_Daemon *TLS_daemon;
    int port;

    if (argc != 2) {
        printf("%s PORT\n", argv[0]);
        return 1;
    }

    port = atoi(argv[1]);
    if ((1 > port) || (port > 65535)) {
        fprintf(stderr, "Port must be a number between 1 and 65535.\n");
        return 1;
    }

    load_keys("localhost", "./httpd.crt", "./httpd.key");

    TLS_daemon = MHD_start_daemon(
        MHD_USE_THREAD_PER_CONNECTION | MHD_USE_INTERNAL_POLLING_THREAD |
            MHD_USE_ERROR_LOG | MHD_USE_TLS,
        (uint16_t)port, NULL, NULL, &http_ahc, NULL,
        MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)120,
        MHD_OPTION_HTTPS_CERT_CALLBACK, &cert_callback, 
        MHD_OPTION_END);
    if (NULL == TLS_daemon) {
        fprintf(stderr, "Error: failed to start TLS_daemon.\n");
        return 1;
    }
    printf("MHD daemon listening on port %u\n", (unsigned int)port);

    (void)getc(stdin);
    MHD_stop_daemon(TLS_daemon);
    return 0;
}

參考連結

2024/10/31

Beyond 粵語歌曲歌單

Beyond 是香港搖滾樂隊,1983 年成立,原為地下樂隊,於 1987 年開始邁入香港主流樂壇,其後憑著多首膾灸人口的經典歌曲如《大地》、《真的愛你》、《光輝歲月》、《海闊天空》等成為華語樂壇其中一隊最具影響力的香港樂隊。

1980 年 8 月,吉他手黃家駒經位於土瓜灣下鄉道的嘉林琴行老闆介紹下認識鼓手葉世榮,聯同鄧煒謙(主音吉他)及李榮潮(貝斯)組成樂隊, 這支樂隊也是 Beyond 的雛型。

1983 年,為了參加《吉他雜誌》舉辦的比賽,Beyond 正式組成。經過幾次人事變動後,1984 年,黃家駒的弟弟黃家強加入成為低音吉他手;1985 年,主音吉他手黃貫中加入。同年,Beyond 在堅道的明愛中心自資舉辦《永遠等待演唱會》,翌年再推出自資卡式帶《再見理想》,之後在經理人陳健添(Leslie Chan)的協助下,從地下音樂正式進入商業流行樂壇。

Beyond 路線的衝突與問題來自於香港的音樂市場本身(背景可以參考盧國沾在 80 年代中期所嘗試發起的非情歌運動), 《亞拉伯跳舞女郎》和《現代舞台》仍然保有 Beyond 獨特風格以及鮮明的音樂特色, 但是唱片銷量並不理想。經紀人也對他們言明如果專輯再不賣, 他們就沒有發片的機會了。第三張專輯《秘密警察》嘗試走向大眾化,《大地》有著強烈東方色彩的Rock,更是深深的唱入了聽眾的心, 成了Beyond的第一首經典名曲;而《喜歡你》成了極受歡迎的情歌之一。這張專輯銷量理想,獲得雙白金的佳績, 而專輯內的《喜歡妳》、《大地》亦成為當時的流行歌曲。

《猶豫》這張 1991 年的專輯發行前,黃家駒曾向唱片公司計劃出一張主題為非洲音樂的專輯,但被唱片公司否決。《Amani》就是原本要做的非洲音樂專輯中的歌曲,再加上其它的歌曲組成專輯,這也造成《猶豫》雖然有一些經典之作,但是歌曲風格並不一致。因此,此唱片的出版,對 Beyond 日後為了追求更多的創作自由而前往日本發展的決定埋下伏筆。

1992 年,Beyond 為了拓展自己的音樂到更多亞洲地區,開始進軍日本及台灣發展。 1992 年 Beyond 開始長居日本,起初以為日本比香港有更大的自由度去玩音樂,惟仍要裝「鄰家男孩」去得到日本樂迷的關注。 雖然他們在日本的生活孤獨,但他們在那裡認識不少的日本音樂風格,成了 Beyond 在日本發展最大的推動力。 主唱黃家駒在 1993 年在日本東京富士電視台錄制遊戲節目《想做甚麼,就做甚麼》上因為節目設計問題跌下舞台而意外去世後, Beyond 其它成員(主音吉他: 黃貫中、貝斯: 黃家強、鼓: 葉世榮)退出日本市場,並在 1994 年與滾石簽五年約,繼續以 Beyond 的名義發展。 滾石給了 Beyond 三子極大的創作空間,並且給予本身的資源支援,這也讓這個時期的 Beyond 探索了更多的音樂風格。

注:1992 年陳健添與 Beyond 因為歌曲版權發生衝突,後來更因為 Beyond 的經理人 Amuse 的介入而惹上官非,雙方關係決裂。 不過陳健添一直擔任 Beyond 的經理人直至黃家駒去世。雖然被歌迷形容為「最受歡迎樂隊的最不受歡迎經紀人」, 但是感覺上陳健添似乎是黃家駒音樂才華的真愛粉(或者說有迷之信任),可以從林子祥的《天地》成果不如預期他說的話感覺的出來 (不過我覺得是編曲的鍋,因為這首歌與《大地》《長城》是一個系列,所以編曲需要偏重東方風格才行,田震同樣曲子改編的《千秋思念》很明顯的更好)。

歌單的歌曲不固定,這只是我現在的歌單。

  • 再見理想

    原為講述舊一代玩樂隊和在夜總會伴奏的樂手的際遇,其中由黃家駒獨唱的版本收錄在 1986 年自資發行的同名專輯《再見理想》中。 由樂隊四名成員共同參與演唱的版本收錄在 Beyond 1988年發行的粵語專輯《秘密警察》中。 歌名可以有二個意思,黃家駒獨唱的版本像是再見了理想,而四名成員合唱的版本則是再見到了理想。 我推薦的是黃家駒獨唱的版本。

  • Myth

    《Myth》收錄在 1986 年自資發行的同名專輯《再見理想》中,由陳時安作詞,黃家駒、陳時安作曲,黃家駒主唱。 陳時安的英文歌詞寫的很好,在他 1985 年因為要出國念書所以退出 Beyond 之後,Beyond 就再也沒有像《Myth》這樣的長篇敘事英文歌曲了。

  • 昔日舞曲

    有一天黃家駒拿著吉他走到街上,經過天橋時,橋下有個乞丐拉住了黃家駒的吉他,這時候 Beyond 還沒有走紅,黃家駒對他說自己也沒有錢給他了。 可是那個乞丐並不是想要錢,黃家駒不懂他的意思。於是乞丐叫黃家駒坐下,向黃家駒訴說了自己昔日的理想,過去從事的事業、奮鬥的歷程以及歷經的輝煌等。 乞丐這番話使得黃家駒感慨頗深,並激勵了年輕的黃家駒在今後的音樂生涯中不斷奮鬥不斷超越自己。於是黃家駒寫了《昔日舞曲》這首歌, 以此紀念那個乞丐與他的談話,表達對音樂理想的不懈追求。這首歌收錄在 EP《永遠等待》中。

  • 永遠等待

    Beyond 演唱的一首重金屬搖滾歌曲,由黃家駒、葉世榮、黃家強、陳時安作詞,黃家駒、陳時安作曲,最早收錄於 Beyond 1986 年 3 月發行的第一張專輯《再見理想》,後收錄在 Beyond 1987 年 7 月發行的同名 EP《永遠等待》中。

  • 亞拉伯跳舞女郎

    Beyond 第一張商業專輯《亞拉伯跳舞女郎》的同名主打歌。 《亞拉伯跳舞女郎》是一張充滿中東風情的概念專輯,專輯中充滿幻象與歷奇的場景。

  • 東方寶藏

    由《Long Way Without Friends》重新填詞並且加上中東音樂風格的版本,由黃家駒、黃貫中作詞,黃家駒作曲,收錄在《亞拉伯跳舞女郎》中。

  • 沙丘魔女

    《沙丘魔女》由黃貫中作詞,黃貫中、黃家強作曲,黃家駒擔任主唱,收錄在《亞拉伯跳舞女郎》中。

  • 無聲的告別

    《無聲的告別》由黃家強作詞,黃家駒、劉志遠作曲,黃家駒擔任主唱,收錄在《亞拉伯跳舞女郎》中。 鍵盤手劉志遠在一起製作了《亞拉伯跳舞女郎》與《現代舞台》二張專輯後離隊,這首歌也是他離隊時其它四人對他唱的歌。

  • 追憶

    《追憶》由黃家駒作詞作曲,黃家駒擔任主唱,收錄在《亞拉伯跳舞女郎》中。

  • 隨意飄蕩

    《隨意飄蕩》由黃家強作詞,黃家駒作曲,黃家駒擔任主唱,收錄在《亞拉伯跳舞女郎》中。 這是 Beyond 比較冷門的歌曲,帶著一種詩意與灑脫的曲風,十分耐聽。

  • 過去與今天

    《過去與今天》由黃家駒作詞作曲,黃家駒擔任主唱,收錄在《亞拉伯跳舞女郎》中。 香港電台電視單元劇《暴風少年》主題曲,為樂隊首次主唱的電視主題曲。

  • 孤單一吻

    《孤單一吻》由黃家強作詞,黃家駒作曲,黃家駒擔任主唱,收錄在《亞拉伯跳舞女郎》中。 這是一首具有西班牙風情的歌曲,吉他部分用了黃家駒十分喜歡的弗拉門戈方法來演繹的。

  • 玻璃箱

    《玻璃箱》由黃家駒作詞作曲,黃家駒擔任主唱,收錄在《亞拉伯跳舞女郎》中。 歌詞以玻璃箱作比喻,形容我們活在都市所面對的困局,憤怒地坐著,獨自地叫喊,也無法衝出去,是一個絕望的世界。

  • 水晶球

    《水晶球》由黃家駒作詞作曲,黃家駒擔任主唱,收錄在《亞拉伯跳舞女郎》中。 《水晶球》的編曲很精彩、充滿張力,內容是黃家駒日後經常觸及的反戰題材。

  • 舊日的足跡

    《舊日的足跡》創作於 1985 年,歌曲靈感來自於黃家駒的一位好友 Mike Lau。 Mike Lau 故鄉在北京,為了學電影而遠赴美國, 十年後終於回到故鄉,感觸頗深。他來到香港後與黃家駒暢談自己的感想,黃家駒便將他這份思念故鄉的情懷寫進《舊日的足跡》一曲之中。 《舊日的足跡》最早收錄於 1986 年自資發行的《再見理想》中,而後在《現代舞台》中也有將前奏改為鋼琴的版本。我喜歡的是改為鋼琴的版本。

  • 天真的創傷

    《天真的創傷》由黃家駒作詞作曲,並且由黃家駒主唱。收錄在 1988 年發行的專輯《現代舞台》中。 一樣是懷念初戀,這首歌聽起來比《喜歡妳》溫暖,是一首溫暖的情歌。

  • 赤紅熱血

    《赤紅熱血》由黃家強作詞,黃家駒作曲,並且由黃家駒主唱。收錄在 1988 年發行的專輯《現代舞台》中。

  • 衝上雲霄

    《衝上雲霄》由黃家強作詞,黃家駒作曲,並且由黃家駒與黃家強主唱。收錄在 1988 年發行的專輯《現代舞台》中。 這是黃家駒及黃家強兄弟首支合唱歌曲。

  • 冷雨夜

    《冷雨夜》由黃家駒作曲。原本黃家駒在完成曲調以後覺得太過商業化而沒有採用,不過黃家強聽到該曲的小樣之後,非常喜歡這首歌,因此自己填詞並挑選了它收錄進 Beyond 樂團的專輯裡。 在 1991 年的演唱會裡,黃家強在《冷雨夜》表演的貝斯獨奏是為人稱道的經典。國語版為《緩慢》。

  • 現代舞台

    《現代舞台》由劉卓輝作詞,黃家駒、黃貫中、劉志遠作曲,黃家駒主唱。收錄在 1988 年發行的專輯《現代舞台》中。

  • 城市獵人

    《城市獵人》由翁偉微作詞,黃家駒作曲,黃家駒主唱。收錄在 1988 年發行的專輯《現代舞台》中。 曲名源自當時日本人氣漫畫《城市獵人》。

  • 大地

    《大地》(1988 年粵語、1990 年國語)由黃家駒作曲,由黃貫中主唱(粵語及國語)。 《大地》歌詞隱喻兩岸中國人長年分隔的骨肉分離的滄桑。

  • 衝開一切

    《秘密警察》由黃貫中作詞,黃家強作曲,黃家駒主唱,收錄在 1988 年發行的專輯《秘密警察》中。 《衝開一切》、《秘密警察》、《喜歡你》幾首歌都是 Beyond 的經典代表作。

  • 秘密警察

    《秘密警察》由黃家駒作詞作曲並且主唱,收錄在 1988 年發行的專輯《秘密警察》中。

  • 未知賽事的長跑

    《未知賽事的長跑》由翁偉微作詞,黃家駒作曲,黃家駒與黃貫中擔任主唱,這是一首重金屬音樂的歌曲,收錄在 1988 年發行的專輯《秘密警察》中。

  • 喜歡妳

    《喜歡妳》是黃家駒寫給和自己已經分手的女友的一首歌。最初做音樂時,由於某些原因黃家駒不得不放棄深愛著的女友, 把時間和精力放到音樂之中,這令他十分愧疚。所以他便寫了《喜歡妳》這首歌,表達對失去愛情的苦楚。

  • 心內心外

    《心內心外》由黃家強作詞作曲,收錄在 1988 年發行的專輯《秘密警察》中。歌曲抒寫對理想愛情的憧憬和等待的寂寞, 以心內與心外的對比來凸顯內心的衝突。

  • 真的愛妳

    《真的愛妳》收錄於《Beyond IV》大碟,為該專輯的主打歌。歌曲以讚頌母愛為主題,表達了對母愛的讚揚。

  • 曾是擁有

    《曾是擁有》收錄於《Beyond IV》大碟,黃家駒作詞作曲,黃家駒主唱。根據網路的說法,黃家駒錄完歌曲小樣後,請好友劉宏博 (Mike Lau) 轉交給前女友潘先麗 Kim。因此可以說這是為前女友潘先麗寫的歌。

  • 摩登時代

    《摩登時代》收錄於《Beyond IV》大碟,翁偉微作詞,葉世榮作曲,黃家駒主唱,林楚麒和聲。這首歌是一首 Beyond 描繪現代都市景象的一首歌,表達了人們不願去承受當下季節的苦悶,內心默默屏蔽周圍的荒謬,只想自醉。

  • 與妳共行

    《與妳共行》收錄於《Beyond IV》大碟,黃家強作詞,黃貫中作曲。這首歌是 TVB 電視劇《淘氣雙子星》第 8 集片尾曲。

  • 逝去日子

    《逝去日子》收錄於《Beyond IV》大碟,劉卓輝作詞,黃家駒作曲。這首歌是 TVB 電視劇《淘氣雙子星》主題曲。

  • 午夜迷牆

    《午夜迷牆》收錄於《Beyond IV》大碟,為電影《黑色迷牆》主題曲,獲得提名第 9 屆香港電影金像獎「最佳電影歌曲」,不過沒有得獎。

  • 歲月無聲

    《歲月無聲》收錄於 1989 年的專輯《真的見證》。此曲原本由麥潔文主唱,最初以情歌風格編曲, 其後由 Beyond 重新以搖滾風格演繹。因為被認為與六四有關,所以成為中國禁曲 (一說是因為版權的關係,所以無法在中國音樂網站搜尋到此曲)。

  • 交織千個心

    《交織千個心》收錄於 1989 年的專輯《真的見證》,由黃家強作詞,黃家駒作曲。 這是專輯中 Beyond 自己演繹為其它人創作的歌曲之一。原唱者為許冠傑,收錄在許冠傑 1988 年發行的專輯《Sam and Friends》中 。

  • 誰是勇敢

    《誰是勇敢》收錄於 1989 年的專輯《真的見證》,由葉世榮作詞,黃家駒作曲,原曲收錄在 1986 年的自資卡式帶《再見理想》內,這是重新灌錄的版本。

  • 勇闖新世界

    《勇闖新世界》收錄於 1989 年的專輯《真的見證》,由梁安琪、侯志強作詞,黃貫中作曲。 這是一首勵志歌曲,葉世榮在這首歌曲表現了自己的優秀鼓技。

  • 又是黃昏

    《又是黃昏》收錄於 1989 年的專輯《真的見證》,由陳健添作詞,黃家駒作曲。 這是專輯中 Beyond 自己演繹為其它人創作的歌曲之一。原唱者為鄧惠欣@小島樂隊。

  • 無悔這一生

    《無悔這一生》收錄於 1989 年的專輯《真的見證》,由盧國宏作詞,黃家駒作曲,為 TVB 電視劇《香港雲起時》主題曲。

  • 午夜怨曲

    《午夜怨曲》收錄於 1989 年的專輯《真的見證》,粵語版由葉世榮作詞,黃家駒作曲。 歌詞反應了 Beyond 組成樂團在出名之前的一些艱辛奮鬥的經歷,從走出地下樂團開始自己出資辦演唱會、出唱片的艱苦歷程,最終沒有放棄過自己的理想。國語版的作詞人為劉卓輝,收錄在 1991 年發行的專輯《光輝歲月》中。

  • 無名的歌

    《無名的歌》收錄於 1989 年的專輯《真的見證》,由黃貫中作詞,黃家駒作曲,這是專輯中 Beyond 自己演繹為其它人創作的歌曲之一。 原唱彭健新,收錄在其 1987 年專輯《我的心》。這首歌是彭健新向 Beyond 邀歌,由 Beyond 作詞作曲與編曲的歌曲, 當時的 Beyond 尚未成名,可以說彭健新是慧眼識英雄。

  • 灰色軌跡

    這首歌是 1990 年劉德華、吳倩蓮主演的電影《天若有情》的插曲。

  • 光輝歲月

    《光輝歲月》的粵語版是一首讚美南非的非洲人國民大會主席納爾遜·曼德拉的歌曲,以歌頌他在南非種族隔離時期為黑人所付出的努力, 當時曼德拉在監禁 28 年後剛被釋放,光輝歲月表達他的一生。在國語版裡面,這首《光輝歲月》是為激勵年輕人努力拼搏而作, 而當中的種族議題被淡化。

  • 俾面派對

    《俾面派對》由黃家駒作曲,黃貫中作詞,收錄於 Beyond 1990 年發行的專輯《命運派對》中。 該曲諷刺了香港演藝圈的古怪現象。

  • 相依的心

    《相依的心》由黃家駒作曲,盧國宏作詞,收錄於 Beyond 1990 年發行的專輯《命運派對》中。

  • 撒旦的詛咒

    《撒旦的詛咒》由黃貫中作曲,葉世榮作詞,並且黃貫中主唱,收錄於 Beyond 1990 年發行的專輯《命運派對》中。 黃家強的拍線令歌的節奏部分很吸引。而背後的吉他節奏用了 Muting 之後,和歌本身的節奏與低音吉他很合櫬。

  • 送給不知怎去保護環境的人 (包括我)

    《送給不知怎去保護環境的人 (包括我) 》由黃家駒作曲,劉卓輝作詞,並且 Beyond 四人主唱。 這首歌是 Beyond 一首呼籲環保的公益歌曲。,收錄於 1990 年發行的專輯《命運派對》中。

  • 戰勝心魔

    《戰勝心魔》由黃家駒作曲,翁偉微作詞,並且 Beyond 四人主唱。 Beyond 樂隊主演電影《開心鬼救開心鬼》主題曲,同時收錄於 1990 年發行的專輯《命運派對》與《戰勝心魔》EP 中。

  • 無淚的遺憾

    《無淚的遺憾》由黃家駒作曲,劉卓輝作詞,收錄於 Beyond 1990 年發行的專輯《命運派對》中。 這是 TVB 電視劇《笑傲在明天》的插曲。

  • Amani

    《Amani》是樂團為呼籲資助非洲難民兒童,呼喚和平而創作的歌曲。

  • 堅持信念

    《堅持信念》為黃家駒作詞作曲,可以視為黃家駒在香港樂壇打拚多年的真實獨白。收錄於 Beyond 1991 年發行的專輯《猶豫》中。

  • 不再猶豫

    《Beyond日記之莫欺少年窮》的主題曲,為 Beyond 的合唱歌曲,同時也是其著名的勵志歌曲。收錄於 Beyond 1991 年發行的專輯《猶豫》中。 國語版為《候診室》。

  • 誰伴我闖蕩

    《Beyond日記之莫欺少年窮》的插曲,國語版為《十字路口》。收錄於 Beyond 1991 年發行的專輯《猶豫》中。

  • 高溫派對

    TVB 綜合節目《Beyond放暑假》主題曲,由胡人(本名陳敏生)作詞,黃家駒作曲,黃家駒、黃貫中、黃家強三人主唱。 收錄於 Beyond 1991 年發行的專輯《猶豫》中。

  • 誰來主宰

    黃家強、黃貫中作詞,黃家駒作曲,黃家強、黃貫中二人主唱,為 TVB 電視劇《笑傲在明天》主題曲,收錄於 Beyond 1991 年發行的專輯《猶豫》中。

  • 完全的擁有

    這是鼓手葉世榮在 Beyond 的第一首主唱歌曲,也是黃家駒去世前的惟一一首。葉世榮作詞,黃家駒作曲,收錄於 Beyond 1991 年發行的專輯《猶豫》中。

  • 報答一生

    《報答一生》由黃家強作曲,劉卓輝作詞,Beyond 編曲,黃貫中演唱,收錄在 Beyond 1992 年發行的精選集《Control》中, 也是惟一的一首新歌。《報答一生》是一首讚頌父愛的歌,其創作的原因是由於Beyond 樂隊成員黃貫中參演的一部電影《老豆唔怕多》,所以樂隊專門為電影創作的歌曲,寫的是一個叛逆的孩子,長大以後終於明白父親的的良苦用心。表達了對父親默默無聞和無私奉獻的讚揚。

  • 半斤八両

    這是收錄《納群星難忘您許冠傑》合輯中的歌曲,因為紀念許冠傑榮休,Beyond 重新編曲翻唱致敬的作品,也是少數 Beyond 的翻唱作品之一(應該也是其翻唱作品中名氣最高的一首)。許冠傑為「香港流行樂壇鼻祖」,與音樂家黎小田、顧嘉煇被視為粵語流行曲的奠基者。

  • 長城

    《長城》由黃家駒作曲及擔當主音、劉卓輝作詞、Beyond 和梁邦彥共同編曲、喜多郎創作前奏;日語版《THE WALL》由真名杏樹填詞; 國語版歌詞由詹德茂改編。粵語版本收錄於Beyond第8張專輯《繼續革命》,並為該大碟之主打歌;日語版本收錄於《超越》,國語版本收錄於《信念》。 黃家駒在一段由香港無綫電視為此曲製作的音樂錄像表示,寫此歌是要「描寫中國人一貫的民族意識」。在歌詞裡,長城反映一個封閉的國度,是強權暴政的產物,是犧牲了無數血肉之軀築成的,然而後人大多只會以它為榮,無視值得反思之處。歌詞是借物描寫這種民族思想和境況,並借古諷今,並非只是寫長城和遠古的中國。 因為被認為與六四有關,以及在香港 2014 「和平佔中」、「雨傘運動」成為抗議者的演唱歌曲,或者被用來影射目前的中國政治, 所以曾經(或者在某一些特別日子)是中國禁曲。

  • 農民

    《農民》原曲為《文武英傑宣言》,而後重新填詞為《農民》,有廣東話和國語部份,內容大抵是描述一個中國農民的生活,如何在艱困的生活中逆境自強。 廣東話和國語的版本卻有著極大不同。廣東話版本是由劉卓輝填詞,是對山區農民生活的影射;而國語版則由姚若龍填詞,內容更為廣泛, 是描述北方人重視固有生活的個性。

  • 不可一世

    Beyond 在香港成名後前經紀人陳健添就更加變本加厲的密密麻麻安排商業娛樂性重的工作,陳健添還因經紀人佣金和分紅和 Beyond 起糾紛, Beyond 早已厭倦陳健添這種為利是圖處處算計的人壓榨逼迫,以及他安排下過的違背意願的奉迎生活, 他們寫的《不可一世》是諷刺逼迫控制他們越緊的經紀人陳健添,而不是陳健添口中所說的諷刺電視台高層。 重新填詞的國語版《今天就做》則是表達了一種生活態度。

  • 遙望

    《遙望》由黃家駒作詞作曲並且擔任主唱。由姚若龍重新填詞的國語版《關心永遠在》收錄在 1992 年的國語專輯《信念》中。

  • 快樂王國

    《快樂王國》是一首輕快的歌曲,黃家強作詞,黃家駒作曲,黃家駒主唱。國語版《年輕》收錄在 1992 年的國語專輯《信念》中。

  • 早班火車

    《早班火車》是一首表達暗戀心情的情歌,黃家駒、黃家強、黃貫中作曲,林振強作詞,黃家駒主唱。

  • 無語問蒼天

    《無語問蒼天》由黃家駒作曲,黃家強作詞,黃家駒主唱。《無語問蒼天》帶著警醒和思考。熱血的赤子被扭曲的眾生包圍, 隨處可見的欺騙和捉弄,慾望和爭鬥。在現實和理想間該如何抉擇?問蒼天,也是問自己。

  • 無盡空虛

    《無盡空虛》由黃家駒作詞、作曲,Beyond 和梁邦彥共同編曲,收錄在 Beyond 1993 年 1 月 7 日由華納唱片發行的 EP《無盡空虛》中。

  • 海闊天空

    《海闊天空》被視為黃家駒最具代表性的遺作,同時也是粵語流行音樂的巅峰之作,收錄在專輯《樂與怒》與國語精選專輯《海闊天空》中。 這首歌是記錄 Beyond 十年心路歷程的歌曲,歌詞承載了黃家駒與樂隊赴日本發展的艱辛與對理想的堅持, 也表達了黃家駒內心深處對香港樂壇的掙扎和失望。曲帶給人們的是一種積極向上的生活態度,堅持自己的理想,永遠不放棄的信念。 當年黃家駒在編寫《海闊天空》一曲時,曾將歌詞其中一句定為「也會怕有一天會跌倒 Oh Yeah」,但黃家強認為意思不對,遂修改為「……Oh No」, 沒料到黃家駒在完成此曲後不足2個月真的意外身亡。

  • 爸爸媽媽

    收錄在專輯《樂與怒》中,由黃貫中作词,黃家駒作曲,Beyond、梁邦彥共同编曲,黃家駒擔任主唱。 Beyond 以中英方爭拗做題材,創作搖滾歌曲《爸爸媽媽》,唱出過渡期主流港人心態,像家庭吵架裡無辜孩子,盼不做用箭靶, 奈何夾在中間,無法自主。這首歌 Beyond 嘗試了不同的音樂風格。在這首歌裡黃家駒還唱了一段 RAP,展示了他過人的節奏感。

  • 狂人山莊

    收錄在專輯《樂與怒》的硬搖滾作品。在專輯《樂與怒》中 Beyond 嘗試了更多的音樂風格, 但是卻仍然能夠被大多數人欣賞,顯示了 Beyond 成熟且優秀的音樂創作能力。

  • 我是憤怒

    收錄在專輯《樂與怒》的硬搖滾作品。由黃貫中作词,黃家駒作曲,Beyond、梁邦彥共同编曲,黃家駒擔任主唱。 該曲的詞作者黃貫中認為,在 Beyond 的眾多經典作品中,只有《我是憤怒》最能代表自己。他認為《我是憤怒》很簡單、很直接,但憤怒不單單是情緒表達,是看到不公平的事情會有所反應,要講出來、要申訴,是種正義感,一種做人的堅守。

  • 全是愛

    收錄在《樂與怒》與精選專輯《海闊天空》的作品,我會放這首是因為我喜歡這首歌的前奏。

  • 命運是你家

    《命運是你家》由黃家駒作曲,黃貫中作詞,Beyond、梁邦彥共同编曲,黃家駒擔任主唱,收錄在《樂與怒》的作品。 這首歌黃家駒稱是對香港九龍皇帝曾灶財的寫照,曾灶財在香港九龍街頭塗鴉的字體自成一派,可以說也是一位傳奇人物。 這是一首民謠風格的歌曲,也可以視為對一個堅持做一件事的人的寫照,而不只是對九龍皇帝而已。

  • 和平與愛

    Beyond 一首關注第三世界的音樂作品,收錄在《樂與怒》與精選專輯《海闊天空》的作品。

  • 完全地愛吧

    《完全地愛吧》為黃家強作詞作曲並且擔任主唱的情歌,收錄在《樂與怒》裡。 日文版為《くちびるを奪いたい(我想奪取你的唇)》,為進軍日本市場的專輯《This Is Love 1》主打歌曲之一。

  • 情人

    《情人》是 Beyond 為各自的愛人創作的歌曲,不過該曲作詞人劉卓輝後來稱,這首原本叫《大陸情人》的歌曲, 其實是藉分隔兩地的感情來隱喻內地與香港的關係。

  • 妄想

    《妄想》由黃家駒作詞,Beyond 四人作曲,Beyond、梁邦彥共同编曲,黃貫中演唱。 這首歌是一首藍調風格的歌曲。

  • 走不開的快樂

    《走不開的快樂》由黃家強作詞,黃家駒作曲,Beyond、梁邦彥共同编曲,黃家駒演唱。 這首歌表達了一種積極的生活態度,曲風則帶有一些日式風格。

  • 無無謂

    《無無謂》由葉世榮作詞作曲,Beyond、梁邦彥共同编曲,Beyond 四人演唱。 曲調採用雷鬼音樂風格,加上 Beoynd 四個人都不是使用本來的聲音,而是使用類似變聲的方式演唱, 讓這首歌曲變成有些特別的搞怪歌曲,也是《樂與怒》專輯的最後一首歌。

  • 總有愛

    《總有愛》是黃家強在 Beyond 經歷黃家駒去世的打擊後創作的歌曲, 他創作這首歌是為了感謝在 Beyond 最困難的時期依然無悔奉獻、關心支持他們的歌迷朋友, 收錄在專輯《二樓後座》中。 二樓後座是指 Beyond 在旺角洗衣街 215 號 Band 房進行創作及彩排的單位,源於成員葉世榮的家庭物業。 國語版為《一輩子陪我走》。

  • 醒你

    《醒你》收錄在專輯《二樓後座》中,林振強作詞,黃家強/黃貫中/葉世榮三人作曲,黃家強主唱。 《醒你》是一首抨擊偶像崇拜文化的歌曲,以犀利的歌詞諷刺了香港人盲目追捧偶像的現象,是對粉絲為了追星的一些行為的不滿和告誡。

  • 遙遠的Paradise

    《遙遠的Paradise》收錄在專輯《二樓後座》中,黃貫中作詞作曲並且擔任主唱。 《遙遠的Paradise》是懷念黃家駒之作。

  • We Don't Wanna Make It Without You

    《We Don't Wanna Make It Without You》收錄在粵語專輯《二樓後座》與國語專輯《Paradise》中,黃貫中作詞作曲,Beyond 三人擔任主唱。 《We Don't Wanna Make It Without You》是懷念黃家駒之作,這是一首半演奏曲,主歌的歌詞部份空白,而副歌的部份則是由當時的 Beyond 三子合唱, 暗示黃家駒一直是 Beyond 主唱,這是一首很出彩的作品。

  • 祝您愉快

    《祝您愉快》收錄在粵語專輯《二樓後座》中,黃家強作詞作曲並且擔任主唱。國語版則收錄在國語專輯《Paradise》中。 這是黃家強懷念黃家駒之作,一首很悲傷的歌曲,很明顯的當時的黃家強尚未走出黃家駒離世的悲傷。

  • 教壞細路

    《教壞細路》由 Beyond 三人填詞,黃家強作曲並演唱,收錄在 Beyond 1995 年 6 月由滾石唱片發行的專輯《Sound》中。「細路」的意思是小孩子。《教壞細路》直白的揭露了彼時香港媒體的過度商業和虛假,也因此歌曲一發行 Beyond 就遭到了香港 TVB 的封鎖。

  • 缺口

    《缺口》由黃貫中作詞作曲並且擔任主唱,收錄在 Beyond 1995 年 6 月由滾石唱片發行的專輯《Sound》中。 這是一首懷念黃家駒之作,充滿了三缺一的遺憾和唏噓。

  • 聲音

    《聲音》由黃貫中作詞作曲並且擔任主唱,收錄在 Beyond 1995 年 6 月由滾石唱片發行的專輯《Sound》中。 當年香港政府在大球場重建後, 實施遭噪音管制, 變相禁止歌手在大球場開演唱會, 這首《聲音》是用來諷刺當時的香港政府。

  • 逼不得已

    《逼不得已》是一首硬搖滾風格的歌曲,由黃貫中作詞,Beyond 三子作曲,黃家強主唱, 收錄在 Beyond 1995 年 6 月由滾石唱片發行的專輯《Sound》中。

  • 大時代

    《大時代》由黃貫中作詞,黃貫中作曲並演唱,收錄在 Beyond 1997 年 4 月由滾石唱片發行的專輯《請將手放開》中。 《請將手放開》是於香港主權移交前發行,專輯是在二樓後座改建後的錄音室灌錄。

  • 回響

    《回響》由黃家強/李焯雄作詞,黃家強作曲並演唱,收錄在 Beyond 1997 年 4 月由滾石唱片發行的專輯《請將手放開》中。 這是為聾人基金會寫的歌曲。

  • 誰命我名字

    《誰命我名字》由黃貫中作詞作曲並演唱,收錄在 Beyond 1997 年 4 月由滾石唱片發行的專輯《請將手放開》中。 這是為保護動物基金會寫的歌曲。

  • 我的知己

    《我的知己》由黃貫中/李焯雄作詞,黃貫中作曲並演唱,收錄在 Beyond 1997 年 4 月由滾石唱片發行的專輯《請將手放開》中。 可以將上一首《我的知己在街頭》這首演奏曲合在一起聽。《我的知己在街頭》也收錄在國語專輯《這裡那裡》中。

  • 回家

    《回家》由黃貫中作詞作曲並演唱,收錄在 Beyond 1997 年 12 月由滾石唱片發行的專輯《驚喜》中。 《回家》體現了1997年港人對香港回歸的迷茫與恐懼與期待。


  • 《霧》由黃偉文作詞,黃貫中作曲並演唱,收錄在 Beyond 1997 年 12 月由滾石唱片發行的專輯《驚喜》中。 《霧》以流行搖滾的曲調呈現,吉他伴奏有重型的夢幻流行,編曲用格調化的手法。 《霧》的歌詞寫的是愛情,在迷霧中讀不懂、看不清,最後只能感嘆歲月已過。


  • 《深》由李焯雄作詞,黃家強作曲並演唱,收錄在 Beyond 1997 年 12 月由滾石唱片發行的專輯《驚喜》中。 《深》鼓機節拍和夢幻流行中,呈現一片乾淨微冷的電子音樂和節拍。

  • 無事無事

    《無事無事》由黃偉文作詞,葉世榮作曲並演唱,收錄在 Beyond 1997 年 12 月由滾石唱片發行的專輯《驚喜》中。 葉世榮使用 Drum Loop 創作《無事無事》,展現了音樂上的另一種新風貌。

  • 不見不散

    《不見不散》這張 1998 年發行的專輯流露出三人創作野心,除了 Beyond 3 位成員外,還有邀請樂隊早期成員劉志遠參與, 此專輯出現三人自己的音樂個性。《不見不散》也是同名專輯中的一首歌曲,由黃貫中作詞作曲。

  • 扯火

    《扯火》收錄在 1998 年發行的專輯《不見不散》。《扯火》由黃家強作詞作曲,以力量搖滾樂曲下的印度歌詠。

  • 崇拜

    《崇拜》收錄在 1998 年發行的專輯《不見不散》。《崇拜》由葉世榮作詞作曲,以電氣搖滾呈現。

  • 十八

    《Good Time》是香港搖滾樂隊 Beyond 在 1999 年發行的專輯,《Good Time》是Beyond宣佈暫時解散前最後一張的專輯。 《十八》是《Good Time》專輯中的一首歌曲,由黃貫中作詞作曲並且擔任主唱。曲調採用民謠風格, 歌詞描述的是經歷風浪後的自己,與十八歲的自己對話的過程。如果是十八歲的自己,會怎麼理解現在的自己呢? 雖然《Good Time》銷量並不是很好,但是《十八》卻是首佳作。

  • 一百零一次

    《一百零一次》是《Good Time》專輯中的一首歌曲,這是一首勵志歌曲,由周耀輝作詞,黃家強作曲,黃家強主唱。

  • 抗戰二十年

    2003 年是 Beyond 成立 20 週年的日子,家強在家駒生前留下的 demo中,選了一首重新製作作為主題曲, 特別之處是歌曲直接用了 demo 中的一段作前奏,由家駒自彈自哼出旋律,到中間其他隊友加入,鑄成四子不可思議的合作。 《抗戰二十年》這首歌與《海闊天空》、《光輝歲月》均被視為 2014 年香港 6.22 民間全民投票、七一大遊行、 學界大罷課與讓愛與和平佔領中環/雨傘革命爭取自由民主的主題歌曲,同時也被一些人認為有暗示六四的歌詞, 所以《抗戰二十年》也是一首中國禁曲。

  • 長空

    《無間道2》的主題曲,也是 Beyond 正式解散前的最後一首歌,由黃家強、葉世榮作詞,黃家強作曲並擔任歌曲主唱,該曲獲得第 23 屆香港電影金像獎最佳原創電影歌曲獎。

2024/08/17

MonetDB

MonetDB is an open-source column-oriented relational database management system (RDBMS) originally developed at the Centrum Wiskunde & Informatica (CWI) in the Netherlands. It is designed to provide high performance on complex queries against large databases, such as combining tables with hundreds of columns and millions of rows.

MonetDB architecture is represented in three layers, each with its own set of optimizers. The front end is the top layer, providing query interface for SQL. Queries are parsed into domain-specific representations, like relational algebra for SQL, and optimized. The generated logical execution plans are then translated into MonetDB Assembly Language (MAL) instructions, which are passed to the next layer. The middle or back-end layer provides a number of cost-based optimizers for the MAL. The bottom layer is the database kernel, which provides access to the data stored in Binary Association Tables (BATs). Each BAT is a table consisting of an Object-identifier and value columns, representing a single column in the database.

MonetDB internal data representation also relies on the memory addressing ranges of contemporary CPUs using demand paging of memory mapped files, and thus departing from traditional DBMS designs involving complex management of large data stores in limited memory.


使用 RPM 安裝以後,使用下列的方式開啟資料庫服務:

sudo systemctl start monetdbd.service

使用下列的方式查詢目前的狀態:

sudo systemctl status monetdbd.service

使用下列的方式停止服務:

sudo systemctl stop monetdbd.service

下面建立一個 demo 資料庫。

sudo monetdb create demo

還需要 release 才能夠使用:

sudo monetdb release demo

然後就可以使用 mclient 連線進行測試:

mclient -u monetdb -d demo

一般而言可以將 user/password 記錄在 .monetdb 中,這樣就不需要輸入帳號密碼,只需要提供資料庫名稱。 下面是內容設定的例子:

user=monetdb
password=monetdb

如果想要刪除資料庫,可以使用 stop 與 destroy:

sudo monetdb stop demo
sudo monetdb destroy demo

使用下列的指令建立一個使用者 danilo:

CREATE USER "danilo" WITH PASSWORD 'danilo' NAME 'Danilo' SCHEMA "sys";
CREATE SCHEMA "danilo" AUTHORIZATION "danilo";
ALTER USER "danilo" SET SCHEMA "danilo";

使用 mclient 連線驗證:

mclient -u danilo demo

MonetDB/e Embedded

MonetDB/e is the embedded version of MonetDB, embed the power of an analytical SQL database engine in your Python or C/C++ applications.

相關連結

2024/08/15

NoSQL (Not only SQL)

一個資料庫在最基礎的層次上需要完成兩件事情:當你把資料交給資料庫時,它應當把資料儲存起來;而後當你向資料庫要資料時,它應當把資料返回給你。

如果要了解 SQL 為什麼重要,我們需要從 NoSQL (Not only SQL) 開始出發。NoSQL 其實是資料庫使用觀念的復古運動、正確觀念的復興運動。 也就是關聯式資料庫並不是唯一且最適的解法,要根據資料的使用特性,選擇適當的儲存機制,所謂的 NoSQL (Not only SQL)。

規模可伸縮性優先考慮是那些必須具備無限可伸縮性的應用,能夠不受限制的擴展比更豐富的功能更加重要。 這些應用包括很多需要高可伸縮性的網站,如 Facebook。 有些網站使用了關聯型資料庫系統,而有些並未採用之。 這些服務的共通性在於對規模可伸縮性的需求比功能更重要,他們無法將應用使用一個單一 RDBMS 解決。 因此,要怎麼處理資料仍然是要不要採用 NoSQL 資料庫的重點(傳統 row-based 的 RDBMS 也有叢集的解決方案, 只是擴張節點相對於某些類型 NoSQL 資料庫而言,比較沒有彈性,不過目前分散式的 RDBMS 解決方案已經出現並且已經有應用實例), 而不是因為某個 NoSQL "awesome" 所以採用,使用了錯誤的資料模型在專案上將容易導致專案的失敗。

比較流行的 NoSQL 資料庫有下列的形式:

  • Key/Value store: map (key, value),是一種簡單的資料模型,適用的情況通常是作為 cache 使用。
  • Document-store: The central concept of a document store is the notion of a "document". 目前比較常見的是 JSON 和 XML 文件。文件模式的資料庫在處理關聯性時反而比較弱, 因為文件應該是 self-contained 的,所以資料之間會有關聯性的情況下儘量不要考慮這個資料模型。
  • Wide column store: For software developers, it can sometimes be helpful to think of them as a key-value collection where each value in the collection is either a simple data type or another key-value collection. For example: map (key, map (key, value)),與傳統的 RDBMS 資料庫(例如 PostgreSQL, CUBRID, MariaDB, MySQL)相比, 適用的範圍是需要良好的可伸縮性與大量資料範圍查詢時的情況。 另外,就是可能會有一個解決方案是使用 SQL 或者是類似的查詢語言來處理與管理 Wide column store 的資料。
  • Graph: This kind of database is designed for data whose relations are well represented as a graph (elements interconnected with an undetermined number of relations between them), 適用於多對多關係(例如社群網站)的情況。與其它模式相比,資料模式十分複雜。 但是注意,多對多關係也不一定只能使用 Graph 資料庫,例如臉書一開始是使用 MySQL 實作。
  • Search Engines:搜尋引擎並不是資料庫,但是可以用來檢索資料並且作為資料庫的輔助工具。 有些搜尋引擎結合文件資料庫或者是 RDBMS 而成為一個完整的資料庫產品。

關聯式資料庫一般而言可以分為二個類型, OLTP (Online transaction processing) 與 OLAP (Online analytical processing), 當然也有試著融合二者的 HTAP 資料庫,不過一般而言還是按照 OLTP 與 OLAP 來分類。 NoSQL 資料庫的使用情況也可以按照這二個類型來分類。

SQL 是為了存取或操作關聯式資料庫所設計的語言。SQL 的出現,解決了以往程式與資料庫相依性過高的問題, 透過 SQL 存取資料庫使得後端資料庫較容易更換(雖然有語法上的相容問題,還是比 NoSQL 資料庫之間的切換簡單), 因此達成資料庫的獨立性,而前端的介面也可獨立使用不同的開發工具。如此將資料層分離出來,儲存到資料庫伺服器, 對於維護與安全都更有保障。

對我來說,過度強調 RDBMS 缺乏良好的可伸縮性是一種 FUD (Fear, Uncertainty, Doubt), 如果一個 RDBMS 其儲存層是建立在分布式系統之上,而且使用 Raft 算法來解決一致性的問題, 那就你就有一個具有良好的可伸縮性的 RDBMS,例如 TiDB。 另外,有一些 SQL solution 使用了 NoSQL 資料庫作為存儲層(雖然有可能不支援或者是僅是有限度的支援 transaction, 不過也不是所有的關聯式資料庫都支援 transaction,一些 OLAP 類型的可能就不支援), 例如 Apache Phoenix 就是建立在 Apache HBase 之上,在這個情況下使用者一樣可以使用 SQL 語言存取 NoSQL 資料庫。

並不是 NoSQL 資料庫就表示有良好的可伸縮性,至少以我所認知的各種資料庫來說, 一般而言為 Wide column store 具有良好的可伸縮性(也就是 Apache HBase 與 Apache Cassandra,以及其它類似技術的資料庫), 其餘形式的資料庫大多數也都是使用傳統 RDBMS 橫向拓展的方法,在可伸縮性這點而言並未具有優勢明顯。 因此對比 RDBMS 來說,NoSQL 資料庫通常是為了解決一些特定的問題而使用,缺點就是程式與資料庫相依性過高(有可能造成被廠商綁定的問題), 缺少一部份 RDBMS 提供的功能而需要自己再造一次輪子,以及安全性上的考量。大部份的情況下還是應該先考慮 RDBMS 是否可以適用。

最後一點,就是用 SQL 代稱某種類型的資料庫是不夠好的說法,畢竟 SQL 是一個查詢語言,因此不應該作為某種資料庫的代稱。 就我個人來說,SQL 這個查詢語言是個很好用的工具,而目前的發展也證明他是個歷久彌新的查詢語言。

CUBRID database

CUBRID 是一個開放原始碼的關係資料庫管理系統, 為高效執行線上交易處理進行了高度優化,特別是需要處理大數據量和高並發請求的複雜商務服務, 支援 Windows 與 Linux 平台。 CUBRID 這個名稱,實際上是兩個單詞的組合:"Cube"(立方體)和"Bride"(橋梁)。 對 CUBRID 而言,"Bride"代表"data bridge"(數據橋), 而"Cube"代表儲存數據的盒子,寓意為放在其中的數據提供安全。

CUBRID 架構特別的地方是加入了 Broker 的設計,Broker 是一個 middleware, 用來處理應用程式與資料庫 server 之間的連線。 會這麼設計的原因是因為 CUBRID 原本是南韓 Naver(Naver 為南韓搜尋引擎龍頭)的內部計畫, 用來取代內部的 Oracle 資料庫以節省資料庫方面日益增加的授權費用, 為了能夠順利過渡,所以加入了 Broker 的設計,而後才公開資料庫的原始碼並成為一個開放原始碼計畫 (Naver 也只有開放 Broker 關於 CRBRID 方面的原始碼)。


CUBRID 使用 NCurses 5 library,如果是已經升到 6 的系統可以安裝相容 ABI 的函式庫,openSUSE Tumbleweed 安裝的指令如下:

sudo zypper in libncurses5

在官網上下載安裝程式以後安裝(我目前使用 11.3),我是安裝在自己的家目錄下。然後使用下列的指令設定環境變數。
如果是使用 sh, bash 或者是 zsh:

source .cubrid.sh

如果是使用 csh 或者是 tcsh:

source .cubrid.csh

如果要啟動 CUBRID 的服務,使用下列的指令:

cubrid service start

如果要停止 CUBRID 的服務,使用下列的指令:

cubrid service stop

在安裝目錄下的 databases 建立一個目錄 testdb,使用下列的指令建立新的資料庫 testdb:

cubrid createdb testdb en_US.utf8

如果需要刪除資料庫,使用下列的指令:

cubrid deletedb testdb

如果要在 database server 開始的時候指定要使用的資料庫,可以這樣執行指令:

cubrid server start testdb

或者是修改 conf 目錄下的 cubrid.conf,加入下面的設定:

# The list of database servers in all by 'cubrid service start' command.
# This property is effective only when the above 'service' property contains 'server' keyword.
server=testdb

(如果使用設定 cubrid.conf 的方式,就不用使用 cubrid server start,而是在 cubrid service start 時就會跟著啟動 server。)


CUBRID 提供了 csql 工具可以用來下達命令。使用 CSQL 連到 testdb 資料庫(使用 dba 帳號):

csql -u dba testdb

管理帳號 dba 預設的密碼為空白,如果想要設置一個密碼,下面是一個範例:

alter user dba password 'dba';

下面是新增一個使用者與設定其密碼的例子:

CREATE USER danilo;
ALTER USER danilo PASSWORD 'danilo';

相關連結

2024/04/28

uHex

uHex 是一個簡單的 hex editor, 原本是 DOS 下的應用程式,但是也可以在支援 POSIX 標準並且有安裝 NCurses 函式庫開發檔案的環境編譯。 主要用來作為觀看二進位檔案以及進行簡單的搜尋與編輯。

因為是個小工具,所以在 Linux 系統我自己採用自行編譯的方式。

下面就是他的操作按鍵:
ALT+H - Help (F1 works too)
ALT+J - Jump to offset
ALT+F - Find an ASCII or HEX string occurence
ALT+S - Save file, applying all modifications
ALT+U - Undo all modifications (reverts the file from disk)
ESC - Quit uHex

2024/04/26

DOSBox

DOSBox 是用於執行適用 MS-DOS 相容作業系統的模擬器 (主要的執行目標是遊戲軟體)。

下面是在 openSUSE Tumbleweed 安裝的指令:

sudo zypper in dosbox

要注意的是,openSUSE Tumbleweed 目前所使用 DOSBox 版本為 DOSBox Staging, 這是自 DOSBox 衍生的一個開發版本,其設定檔案在家目錄下的 .config/dosbox/dosbox.conf。

在按鍵部份,如果要切換到 full-screen(或者是切換回來),使用下列的按鍵組合: ALT-ENTER
如果要關閉 DOSBos: CTRL-F9
捕抓或者是釋放滑鼠: CTRL-F10

我習慣建立一個 DOSBox 目錄作為 c:

[autoexec]
# Lines in this section will be run at startup.
# You can put your MOUNT lines here.
MOUNT C /home/danilo/DOSBox/C -freesize 10240
c:

下面是其它的設定:
fullresolution = original
output = openglnb
memsize = 128
aspect = stretch
gus = false
(openSUSE Tumbleweed 的預設值 gus 是 true,需要設為 false,不然音訊輸出會不正常)

如果要掛載一個 iso 檔案,下面是一個例子:

imgmount d "/home/danilo/DOSBox/ISO/MyISO.iso" -t iso

2024/04/22

PostgreSQL

PostgreSQL是一個開源的物件型關聯式資料庫管理系統,在類似 BSD 授權與 MIT 授權的 PostgreSQL 授權下發行。

Install on openSUSE Tumbleweed

如果不是首次安裝而是升級,需要使用 pg_dump 執行 dump the databases,先對目前的資料進行備份。

首先是安裝(在這裡是 PostgreSQL 16):

sudo zypper install postgresql16-server postgresql16 postgresql16-devel \
postgresql16-contrib postgresql-devel
如果需要設定 postgres 的密碼,
sudo passwd postgres
先切換到 root,再切換到 postgres,
sudo su postgres
然後 init database:
/usr/bin/initdb /var/lib/pgsql/data
如果要手動開啟服務,使用:
/usr/bin/pg_ctl -D /var/lib/pgsql/data -l logfile start
關閉:
/usr/bin/pg_ctl -D /var/lib/pgsql/data -l logfile stop
或者是使用下列的方式開啟服務:
sudo service postgresql start
關閉服務:
sudo service postgresql stop

如果要開機的時候就啟動服務,使用:
sudo chkconfig postgresql on
如果不要,使用:
sudo chkconfig postgresql off

安裝完成以後,可以使用 psql postgres (使用者登入要使用 postgres)來確定目前的設定檔:
SHOW config_file;
目前的 server 版本:
SHOW server_version;
如果要使用 psql 列出目前的 database,使用 \l 命令:
\l
如果要查詢目前的使用者連線:
SELECT * FROM pg_stat_activity;
Digging into the currently opened database in psql one can use the \d command.
\d

Add user to PostgreSQL

如果要使用指令增加使用者和資料庫(需要切換使用者身份到 postgres):
createuser danilo

使用 PostgreSQL SQL shell (psql) 增加使用者。

1. Add a user called danilo

Type the following command to create a user called danilo with a password called danilo:
template1=# CREATE USER danilo WITH PASSWORD 'danilo';

2. Add a database called danilo

Type the following command:
template1=# CREATE DATABASE danilo WITH OWNER danilo ENCODING 'UTF8';

3. Now grant all privileges on database

template1=# GRANT ALL PRIVILEGES ON DATABASE danilo to danilo;


如果要改使用者密碼,下面是 user danilo 的範例:
template1=# ALTER USER "danilo" WITH PASSWORD 'danilo';

或者是使用下列的方式設定 postgres:
template1=# \password postgres

要讓密碼生效,要修改 /var/lib/pgsql/data (或者是指定的 PostgreSQL DATADIR) 目錄下的 pg_hba.conf。 預設的認證方式為 trust,這表示不會做任何的密碼檢查,如果要開啟密碼檢查, 要把 trust 改為 scram-sha-256 或者是其它的認證方式(例如 md5 或者是 peer)。

如果設為 peer 就只會讓 local 同樣的 username 連線。
在需要讓其它的本機使用者連線的情況下,可以改為 scram-sha-256,下面是一個例子:

# "local" is for Unix domain socket connections only
local   all             all                                     scram-sha-256
# IPv4 local connections:
host    all             all             127.0.0.1/32            scram-sha-256
# IPv6 local connections:
host    all             all             ::1/128                 scram-sha-256

如果想知道目前的 username 與 password,可以使用 psql 查詢 pg_shadow 以後列出來:

psql -Atq -U postgres -d postgres -c "SELECT concat('\"', usename, '\" \"', passwd, '\"') FROM pg_shadow"

如果要查詢目前的使用者列表:

\du

如果要設定某一個使用者為 Superuser:

ALTER USER danilo WITH SUPERUSER;

如果要設定某一個使用者為 No Superuser

ALTER USER danilo WITH NOSUPERUSER;

Configuration

PostgreSQL uses three main configuration files to control overall operations. You can find these files in the initialized data cluster (the folder specified during the initialization process using initdb -d).

如果查詢 source code,我們發現 PostgreSQL 設定來源有14種,如default、environment variable、configuration file、client等,具體如下:
const char *constGucSource_Names[] =
{
        /* PGC_S_DEFAULT */ "default",
        /* PGC_S_DYNAMIC_DEFAULT */ "default",
        /* PGC_S_ENV_VAR */ "environment variable",
        /* PGC_S_FILE */ "configuration file",
        /* PGC_S_ARGV */ "command line",
        /* PGC_S_GLOBAL */ "global",
        /* PGC_S_DATABASE */ "database",
        /* PGC_S_USER */ "user",
        /* PGC_S_DATABASE_USER */ "database user",
        /* PGC_S_CLIENT */ "client",
        /* PGC_S_OVERRIDE */ "override",
        /* PGC_S_INTERACTIVE */ "interactive",
        /* PGC_S_TEST */ "test",
        /* PGC_S_SESSION */ "session"
};

如果無法確定設定的來源,可以使用下列的 SQL 述句查詢:

select name, setting, source from pg_settings;

There are several different types of configuration settings, divided up based on the possible inputs they take

  • Boolean: true, false, on, off
  • Integer: Whole numbers (2112)
  • Float: Decimal values (21.12)
  • Memory / Disk: Integers (2112) or "computer units" (512MB, 2112GB). Avoid integers--you need to know the underlying unit to figure out what they mean.
  • Time: "Time units" aka d,m,s (30s). Sometimes the unit is left out; don't do that
  • Strings: Single quoted text ('pg_log')
  • ENUMs: Strings, but from a specific list ('WARNING', 'ERROR')
  • Lists: A comma separated list of strings ('"$user",public,tsearch2)

下面 14 個項定項目是大多數的使用者可能會需要設定的項目:

  1. listen_address
  2. max_connections
  3. shared_buffers
  4. work_mem
  5. maintenance_work_mem
  6. max_fsm_pages
  7. synchronous_commit
  8. checkpoint_segments
  9. wal_buffers
  10. autovacuum
  11. effective_cache_size
  12. default_statistics_target
  13. constraint_exclusion
  14. log_destination & log settings

之所以需要設定一些項目的理由,在於根據自己機器的狀況設定更好的設定值, 可以讓 PostgreSQL 執行的更好。

Each backend process allocates a local memory area for query processing; each area is divided into several sub-areas – whose sizes are either fixed or variable.

  • work_mem: Executor uses this area for sorting tuples by ORDER BY and DISTINCT operations, and for joining tables by merge-join and hash-join operations.
  • maintenance_work_mem: Some kinds of maintenance operations (e.g., VACUUM, REINDEX) use this area.
  • temp_buffers: Executor uses this area for storing temporary tables.

A shared memory area is allocated by a PostgreSQL server when it starts up. This area is also divided into several fix sized sub-areas.

  • shared buffer pool: PostgreSQL loads pages within tables and indexes from a persistent storage to here, and operates them directly.
  • WAL buffer: To ensure that no data has been lost by server failures, PostgreSQL supports the WAL mechanism. WAL data (also referred to as XLOG records) are transaction log in PostgreSQL; and WAL buffer is a buffering area of the WAL data before writing to a persistent storage.
  • commit log: Commit Log(CLOG) keeps the states of all transactions (e.g., in_progress,committed,aborted) for Concurrency Control (CC) mechanism.

如果要查詢目前的設定,可以使用下面的命令:

SHOW ALL;

如果要查詢單項,以 work_mem 來說:

show work_mem;
或者是查詢 pg_settings (a “system view” can be queried),
SELECT * FROM pg_settings WHERE name = 'work_mem';

All these can be edited with a text editor.

File Purpose
postgresql.conf Controls the listening port, IP, and default query planner settings, memory settings, path settings, and logging settings. Can be queried via pg_settings database view.
pg_hba.conf Controls the authentication models used by PostgreSQL and can be set per user, per database, per IP range, or a combination of all.
pg_indent.conf Controls mapping of an OS user to a PostgreSQL user.

postgresql.conf

The following settings are all located in the postgresql.conf file. Remember that these are default settings; many of these you can choose to override for each session, for each database, or for each user/role.

Option Description
listen_addresses Use ‘*’ to listen on all IPs of the server, ‘localhost’ to listen on just local, or a comma separated list of IPs to listen on. Requires service restart if changed and can only be set globally.
port Defaults to 5432, but can be changed to allow multiple postgresql daemon clusters/versions to coexist using same IP but different ports.
search_path List of default schemas that don’t need schema qualification. First schema is where non-schema qualified objects are created.
constraint_exclusion Options: on, off, or partial. Partial was introduced in 8.4 and is the new default. Allows planner to skip over tables if constraint ensures query conditions cannot be satisfied by the table. Mostly used for table partitioning via table inheritance.
shared_buffers Controls how much memory is allocated to PostgreSQL and shared across all processes. Requires service restart and can only be set globally.

In PostgreSQL 9.4, a new SQL construction ALTER SYSTEM was introduced that allows you to set these settings at the system level without editing the postgresql.conf. For many, you still need to do a service restart and for others at least a:

SELECT pg_reload_conf();

pg_hba.conf

PostgreSQL supports many authentication schemes to control access to the database. The pg_hba.conf file dictates which schemes are used based on the rules found in this file. You can mix and match various authentication schemes at the same time. The rules are applied sequentially such that the first match fitting a connection is the one that is used. This is important to remember because if you have a more restrictive rule above a less restrictive, then the more restrictive is the one that trumps.

The most commonly used authentication schemes are trust (which allows connections without a password) and md5 (which authenticates with md5 encrypted passwords). Others include: reject, crypt, password (this is plain text), krb5, ident (authenticate simply by identity of user in OS), pam, and ldap.


如果我們需要每個 query 的執行時間,可以在 psq 使用下列的命令開啟設定(預設值有可能是關閉):
\timing

Tools

PostgreSQL comes bundled with several tools useful for administration and query writing.

Tool Description
psql Command-line client packaged with PostgreSQL. Good for automating SQL jobs, copying data, outputing simple HTML reports.
createdb, dropdb For creating and dropping a database from the OS shell.
pgAdminIII Popular graphical user interface packaged with PostgreSQL.
pg_restore Command-line tool for restoring compressed or .tar backups.
pg_dump Command-line tool for doing backups. Great for automated backups.
pg_dumpall Command-line tool for dumping all databases into a single backup.
pgAgent A daemon/service that can be downloaded from http://www.pgadmin.org/download/pgagent.php. Used for scheduling SQL jobs and batch shell jobs. Jobs can be added easily and monitored using the PgAdmin III job interface.
pg_basebackup Used for doing filesystem hot backup of db data cluster.
pg_upgrade Used for updating in place from one major version of PostgreSQL to another.

SQL statement

Auto-increment column

PostgreSQL 並不支援標準語法,而是提供一個自己實作的近似語法:
CREATE TABLE t1 (col1 SERIAL PRIMARY KEY);

UPSERT

在 PostgreSQL 15 之前 PostgreSQL 的 UPSERT 語法並未採用標準的 merge,而是使用自己實作的語法。 不過在 PostgreSQL 15 開始,就已經支援 SQL 的 merge 語法。

舉例來說,我們建立下面一個表格:

create table notes (title varchar(255), body text, modified timestamp, primary key(title));
於是可以使用 ON CONFLICT DO UPDATE SET 方式來決定是要 update 或者是 insert(PS. 舉例是用 GOLANG 的常數宣告):
        const insertString = `
              insert into notes (title, body, modified)
              values($1, $2, now()) ON CONFLICT (title)
              DO UPDATE SET (body,modified) = (EXCLUDED.body,now())`

Views

You can create a view over the query, which gives a name to the query that you can refer to like an ordinary table:
CREATE VIEW myview AS
    SELECT city, temp_lo, temp_hi, prcp, date, location
        FROM weather, cities
        WHERE city = name;

SELECT * FROM myview;

Making liberal use of views is a key aspect of good SQL database design. Views allow you to encapsulate the details of the structure of your tables, which might change as your application evolves, behind consistent interfaces.

Foreign Keys

Foreign Keys 是維護你資料的 referential integrity 的方法,當插人一個新資料的時候,確定參考的資料是存在的。

下面是一個使用的範例:
CREATE TABLE cities (
        city     varchar(80) primary key,
        location point
);

CREATE TABLE weather (
        city      varchar(80) references cities(city),
        temp_lo   int,
        temp_hi   int,
        prcp      real,
        date      date
);

Unique Constraints

Unique constraints ensure that the data contained in a column, or a group of columns, is unique among all the rows in the table. The syntax is:

CREATE TABLE products (
product_no integer UNIQUE,
name text,
price numeric
);
when written as a column constraint, and:
CREATE TABLE products (
product_no integer,
name text,
price numeric,
UNIQUE (product_no)
);

Transactions

In PostgreSQL, a transaction is set up by surrounding the SQL commands of the transaction with BEGIN and COMMIT commands.

By default (without BEGIN), PostgreSQL executes transactions in "autocommit" mode, that is, each statement is executed in its own transaction and a commit is implicitly performed at the end of the statement (if execution was successful, otherwise a rollback is done).

Window Functions

A window function performs a calculation across a set of table rows that are somehow related to the current row. This is comparable to the type of calculation that can be done with an aggregate function. But unlike regular aggregate functions, use of a window function does not cause rows to become grouped into a single output row — the rows retain their separate identities. Behind the scenes, the window function is able to access more than just the current row of the query result.

A window function call always contains an OVER clause directly following the window function's name and argument(s). This is what syntactically distinguishes it from a regular function or aggregate function. The OVER clause determines exactly how the rows of the query are split up for processing by the window function. The PARTITION BY list within OVER specifies dividing the rows into groups, or partitions, that share the same values of the PARTITION BY expression(s). For each row, the window function is computed across the rows that fall into the same partition as the current row.

下面是一個範例:
SELECT depname, empno, salary,
       rank() OVER (PARTITION BY depname ORDER BY salary DESC)
FROM empsalary;
結果:
  depname  | empno | salary | rank
-----------+-------+--------+------
 develop   |     8 |   6000 |    1
 develop   |    10 |   5200 |    2
 develop   |    11 |   5200 |    2
 develop   |     9 |   4500 |    4
 develop   |     7 |   4200 |    5
 personnel |     2 |   3900 |    1
 personnel |     5 |   3500 |    2
 sales     |     1 |   5000 |    1
 sales     |     4 |   4800 |    2
 sales     |     3 |   4800 |    2
(10 rows)

As shown here, the rank function produces a numerical rank within the current row's partition for each distinct ORDER BY value, in the order defined by the ORDER BY clause. rank needs no explicit parameter, because its behavior is entirely determined by the OVER clause.

The rows considered by a window function are those of the "virtual table" produced by the query's FROM clause as filtered by its WHERE, GROUP BY, and HAVING clauses if any. For example, a row removed because it does not meet the WHERE condition is not seen by any window function. A query can contain multiple window functions that slice up the data in different ways by means of different OVER clauses, but they all act on the same collection of rows defined by this virtual table.

Inheritance

Inheritance is a concept from object-oriented databases. It opens up interesting new possibilities of database design.

下面是一個使用的範例:
CREATE TABLE cities (
  name       text,
  population real,
  altitude   int     -- (in ft)
);

CREATE TABLE capitals (
  state      char(2)
) INHERITS (cities);
當你查詢的時候,會列出 cities 和 capitals 的結果,如果只要 cities 的部份,下面是一個使用的範例:
SELECT name, altitude
    FROM ONLY cities
    WHERE altitude > 500;

WITH Queries (Common Table Expressions)

WITH 是用來建立暫存的資料集,稱為通用資料表運算式 CTE (common table expression) 的暫存具名結果集。 通用資料表運算式可以包括指向本身的參考。 這稱為遞迴通用資料表運算式。

下面的資料來自於 PostgreSQL 網站

In PostgreSQL, the WITH query provides a way to write auxiliary statements for use in a larger query. It helps in breaking down complicated and large queries into simpler forms, which are easily readable.

The WITH query being CTE query, is particularly useful when subquery is executed multiple times. It is equally helpful in place of temporary tables.

The optional RECURSIVE modifier changes WITH from a mere syntactic convenience into a feature that accomplishes things not otherwise possible in standard SQL. Using RECURSIVE, a WITH query can refer to its own output. A very simple example is this query to sum the integers from 1 through 100:

WITH RECURSIVE t(n) AS (
    VALUES (1)
  UNION ALL
    SELECT n+1 FROM t WHERE n < 100
)
SELECT sum(n) FROM t;

The general form of a recursive WITH query is always a non-recursive term, then UNION (or UNION ALL), then a recursive term, where only the recursive term can contain a reference to the query's own output. Such a query is executed as follows:

  1. Evaluate the non-recursive term. For UNION (but not UNION ALL), discard duplicate rows. Include all remainingrows in the result of the recursive query, and also place them in a temporary working table.

  2. So long as the working table is not empty, repeat these steps:

    1. Evaluate the recursive term, substituting the current contents of the working table for the recursive self-reference. For UNION (but not UNION ALL), discard duplicate rows and rows that duplicate any previous result row. Include all remaining rows in the result of the recursive query, and also place them in a temporary intermediate table.

    2. Replace the contents of the working table with the contents of the intermediate table, then empty the intermediate table.

Recursive queries are typically used to deal with hierarchical or tree-structured data.
WITH RECURSIVE included_parts(sub_part, part, quantity) AS (
    SELECT sub_part, part, quantity FROM parts WHERE part = 'our_product'
  UNION ALL
    SELECT p.sub_part, p.part, p.quantity
    FROM included_parts pr, parts p
    WHERE p.part = pr.sub_part
  )
SELECT sub_part, SUM(quantity) as total_quantity
FROM included_parts
GROUP BY sub_part

Index

下面是一個建立 index 的範例:
CREATE TABLE test1c (
    id integer,
    content varchar COLLATE "x"
);

CREATE INDEX test1c_content_index ON test1c (content);

GIN and GiST Index Types

There are two kinds of indexes that can be used to speed up full text searches. Note that indexes are not mandatory for full text searching, but in cases where a column is searched on a regular basis, an index is usually desirable.

CREATE INDEX name ON table USING GIN (column);
and:
CREATE INDEX name ON table USING GIST (column);

GIN indexes are the preferred text search index type. As inverted indexes, they contain an index entry for each word (lexeme), with a compressed list of matching locations. Multi-word searches can find the first match, then use the index to remove rows that are lacking additional words. GIN indexes store only the words (lexemes) of tsvector values, and not their weight labels. Thus a table row recheck is needed when using a query that involves weights.

A GiST index is lossy, meaning that the index might produce false matches, and it is necessary to check the actual table row to eliminate such false matches. (PostgreSQL does this automatically when needed.) GiST indexes are lossy because each document is represented in the index by a fixed-length signature. The signature is generated by hashing each word into a single bit in an n-bit string, with all these bits OR-ed together to produce an n-bit document signature. When two words hash to the same bit position there will be a false match. If all words in the query have matches (real or false) then the table row must be retrieved to see if the match is correct.

Lossiness causes performance degradation due to unnecessary fetches of table records that turn out to be false matches. Since random access to table records is slow, this limits the usefulness of GiST indexes. The likelihood of false matches depends on several factors, in particular the number of unique words, so using dictionaries to reduce this number is recommended.

Full Text Search

Full text searching in PostgreSQL is based on the match operator @@, which returns true if a tsvector (document) matches a tsquery (query).

下面是一個使用的例子:
SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector @@ 'cat & rat'::tsquery;
 ?column?
----------
 t

SELECT 'fat & cow'::tsquery @@ 'a fat cat sat on a mat and ate a fat rat'::tsvector;
 ?column?
----------
 f
另外的例子:
SELECT to_tsvector('fat cats ate fat rats') @@ to_tsquery('fat & rat');
 ?column?
----------
 t
The @@ operator also supports text input, allowing explicit conversion of a text string to tsvector or tsquery to be skipped in simple cases. The variants available are:
tsvector @@ tsquery
tsquery  @@ tsvector
text @@ tsquery
text @@ text
It is possible to do a full text search without an index. A simple query to print the title of each row that contains the word friend in its body field is:
SELECT title
FROM pgweb
WHERE to_tsvector('english', body) @@ to_tsquery('english', 'friend');
PostgreSQL 可以使用 GIN index 來加快 text search 的速度:
CREATE INDEX pgweb_idx ON pgweb USING GIN (to_tsvector('english', body));

UUID

可以安裝 uuid-ossp 模組來處理 UUID。下面是安裝的方式(需要有 Superuser 的權限):

CREATE EXTENSION "uuid-ossp";

於是就可以使用下列的方式生成 UUID:

select uuid_generate_v4();

或者也可以使用下列的方式生成 UUID:

select (md5(random()::text || clock_timestamp()::text))::uuid;

NoSQL 與 PostgreSQL

PostgreSQL 提供了可選的 HStore key-value store extension, 並且 HStore extension 具有將 key-value store 轉為 JSON 的能力(hstore_to_json function)。

測試 PostgreSQL 是否有支援 hstore(使用 CREATE EXTENSION 語句):
package require tdbc::postgres
tdbc::postgres::connection create db -user postgres -password postgres -port 5432

set statement [db prepare {
     CREATE EXTENSION hstore
}]

$statement foreach row {
 puts $row
}

$statement close
db close
下面是一個使用的範例:
CREATE TABLE products (
  id serial PRIMARY KEY,
  name varchar,
  attributes hstore
);
From here you can insert whatever you want into the attributes column. And then query based on those various keys or values.
INSERT INTO products (name, attributes) VALUES (
 'Geek Love: A Novel',
 'author    => "Katherine Dunn",
  pages     => 368,
  category  => fiction'
);

SELECT name, attributes->'author' as author
FROM products
WHERE attributes->'category' = 'fiction'
The obvious benefit here is flexibility, but where it really shines is being able to leverage various index types. In particular, a GIN or GiST index will index every key and value within the hstore. This way when you filter on something it’ll use the index if it makes sense to the planner within Postgres.

PostgreSQL and JSON

在 PostgreSQL 9.2 (及以後)版本中,查詢結果可以轉換為 JSON data type 後返回。 PostgreSQL 也增加了 PLv8 extension,可以使用 JavaScript 來撰寫 PostgreSQL stored procedure。

That has changed with 9.4, with the introduction of the JSONB data type, which stores JSON data in binary form, such that it is both more compact and more efficient than the textual form. Moreover, the same GIN and GIST indexes that now are able to work so well with HStore data also are able to work well, and quickly, with JSONB data. So you can search for and retrieve text from JSONB documents as easily (or more) as would have been the case with a document database, such as MongoDB.

PostgreSQL provides two native operators -> and ->> to help you query JSON data.
  • The operator -> returns JSON object field by key.
  • The operator ->> returns JSON object field by text.
下面是一個使用的例子:
CREATE TABLE integrations (id UUID, data JSONB);
INSERT INTO integrations VALUES (
  uuid_generate_v4(),
  '{
    "service": "salesforce",
    "id": "AC347D212341XR",
    "email": "craig@citusdata.com",
    "occurred_at": "8/14/16 11:00:00",
    "added": {
      "lead_score": 50
    },
    "updated": {
      "updated_at": "8/14/16 11:00:00"
    }
    }')
下面是一個最簡單的例子:
CREATE TABLE people(
    id serial primary key,
    data jsonb not null
);

INSERT INTO people(data) VALUES ('{
    "name": "Bob",
    "addresses": [
        {
            "street": "Centre",
            "streetnumber": 24,
            "town": "Thornlie",
            "state": "WesternAustralia",
            "country": "Australia"
        },
        {
            "street": "Example",
            "streetnumber": "4/311-313",
            "town": "Auckland",
            "country": "NewZealand"
        }
    ],
    "phonenumbers": [
        {
            "type": "mobile",
            "number": "12345678"
        }
    ]
}');

INSERT INTO people(data) VALUES ('{
  "name": "Fred",
  "phonenumbers": [
    { "type": "mobile", "number": "12345678" }
  ]
}');

INSERT INTO people(data) VALUES ('{
  "name": "John Doe"
}');
下面則是使用 inner join 查詢 phonenumbers 的做法:
select
  p1.id AS person1,
  p2.id AS person2,
  p1.data ->> 'name' AS "p1 name",
  p2.data ->> 'name' AS "p2 name",
  pns1 ->> 'type' AS "type",
  pns1 ->> 'number' AS "number"
from people p1
  inner join people p2
    on (p1.id > p2.id)
  cross join lateral jsonb_array_elements(p1.data -> 'phonenumbers') pns1
  inner join lateral jsonb_array_elements(p2.data -> 'phonenumbers') pns2
    on (pns1 -> 'type' = pns2 -> 'type' AND pns1 -> 'number' = pns2 -> 'number');
The ? operator will tell us if some part of JSON has a top level key:
SELECT *
FROM companies
WHERE data->'company'->'tags' ? 'B2B'
如果要回來的 JSONB 結果比較漂亮,可以使用:
SELECT jsonb_pretty(data)
FROM companies;

PostgreSQL and XML

PostgreSQL 提供了 XML data type and support functions 的功能 (SQL/XML), 並且提供了一定程度的 XPATH 支援(xpath_exists function) 與輸出 XML 文件的能力,因此也可以考慮將 PostgreSQL 作為一個 XML 文件資料庫使用。

下面是一個 PostgreSQL XPath function 處理 XML 字串的例子(要注意例子中在 xpath() 的第三個參數設定了 namespace 的別名 n):
WITH x AS ( SELECT
'<promotions xmlns="http://www.demandware.com/xml/impex/promotion/2008-01-31">
    <campaign campaign-id="2013-1st-semester-jet-giveaways">
        <description>2013 1st Semester Jet Giveaways</description>
        <enabled-flag>true</enabled-flag>
        <start-date>2013-01-01T05:00:00.000Z</start-date>
        <end-date>2013-07-01T04:00:00.000Z</end-date>
        <customer-groups>
            <customer-group group-id="Everyone"/>
        </customer-groups>
    </campaign>
 </promotions>'::xml AS t
)
SELECT xpath('/n:promotions/n:campaign/n:description/text()', t
           , '{{n,http://www.demandware.com/xml/impex/promotion/2008-01-31}}')
FROM   x;

List extensions

如果要列出目前系統上的 PostgreSQL extension name(使用 TDBC):

package require tdbc::postgres
tdbc::postgres::connection create db -user postgres -password postgres -port 5432

set statement [db prepare {
     select extname from pg_extension
}]

$statement foreach row {
 puts $row
}

$statement close
db close

List tables

使用 psql 列出 schema public 下的所有 table:
\dt public.*
You can select the tables from information_schema:
SELECT * FROM information_schema.tables WHERE table_schema = 'public';
或者也可以使用下列的方式:
select * from pg_tables where schemaname='public';

Client

C API (libpq)

The libpq library is the C interface to PostgreSQL. It is a set of library functions that allow client programs to interact with PostgreSQL.

下面是一個使用的範例,例印 PostgreSQL libpq 的版本:
#include <libpq-fe.h>

int main() {

    int lib_ver = PQlibVersion();

    printf("Version of libpq: %d\n", lib_ver);

    return 0;
}
使用下列的方式得到 include/library path:
$ pg_config --includedir
/usr/include/postgresql
$ pg_config --libdir
/usr/lib/postgresql95/lib64
所以可以使用下列的方式編譯:
gcc lib_version.c -I`pg_config --includedir` -L`pg_config --libdir` -lpq -std=c99 -o test

再來是列印 PostgreSQL server 的版本:
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>

void do_exit(PGconn *conn) {

    PQfinish(conn);
    exit(1);
}

int main() {

    PGconn *conn = PQconnectdb("user=postgres password=postgres dbname=postgres");

    if (PQstatus(conn) == CONNECTION_BAD) {

        fprintf(stderr, "Connection to database failed: %s\n",
            PQerrorMessage(conn));
        do_exit(conn);
    }

    int ver = PQserverVersion(conn);

    printf("Server version: %d\n", ver);

    PQfinish(conn);

    return 0;
}

下面是一個簡單的 query 程式:
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>

void do_exit(PGconn *conn) {

    PQfinish(conn);
    exit(1);
}

int main() {

    PGconn *conn = PQconnectdb("user=postgres password=postgres dbname=postgres");i

    if (PQstatus(conn) == CONNECTION_BAD) {

        fprintf(stderr, "Connection to database failed: %s\n",
            PQerrorMessage(conn));
        do_exit(conn);
    }

    PGresult *res = PQexec(conn, "SELECT VERSION()");

    if (PQresultStatus(res) != PGRES_TUPLES_OK) {

        printf("No data retrieved\n");
        PQclear(res);
        do_exit(conn);
    }

    printf("%s\n", PQgetvalue(res, 0, 0));

    PQclear(res);
    PQfinish(conn);

    return 0;
}

ODBC (Linux/openSUSE)

下載 PostgreSQL ODBC Driver 編譯並且安裝以後,

unixODBC 需要設定二個檔案:/etc/unixODBC/odbc.ini/etc/unixODBC/odbcinst.ini。 下面則是我目前 PostgreSQL 的設定。

odbcinst.ini:

[PSQL]
Description=PostgreSQL
Driver64=/usr/lib64/psqlodbcw.so
UsageCount=1

odbc.ini:

[PostgreSQL]
Description=PostgreSQL Data Source
Driver=PSQL
Database=postgres
UserName=postgres
Password=postgres
Host=localhost
Port=5432
ReadOnly=No
ShowSystemTables=No

下面就是測試的 Tcl script:

package require tdbc::odbc

set connStr "DSN=PostgreSQL; UID=postgres; PWD=postgres;"
tdbc::odbc::connection create db $connStr

set statement [db prepare {
    SELECT VERSION()
}]

$statement foreach row {
    puts [dict get $row version]
}

$statement close
db close

JDBC

PostgreSQL 提供了 JDBC driver 可以使用。

下面是使用 TDBCJDBC 的範例:
package require tdbc::jdbc

set className    {org.postgresql.Driver}
set url          jdbc:postgresql://localhost:5432/danilo
set username     danilo
set password     danilo

tdbc::jdbc::connection create db $className $url $username $password -readonly 0

set statement [db prepare {select extname, extversion from pg_extension}]
puts "List extension name and version:"
$statement foreach row {
    puts "[dict get $row extname] - [dict get $row extversion]"
}

$statement close

db close

Node.js

我們使用 node-postgres 來連線。

首先建立一個目錄 pg-test,並且在目錄下執行

npm init -y

這會建立一個 package.json 檔案,我們可以對需要修改的部份再進行修改。修改 package.json,加入下列的設定:

  "type": "module",

再來使用 NPM 安裝 node-postgres:

npm install pg --save

建立 index.js,內容如下:

import pg from 'pg';

const config = {
    host: 'localhost',
    user: 'danilo',
    password: 'danilo',
    database: 'danilo',
    port: 5432,
    ssl: false
};

const client = new pg.Client(config);

client.connect(err => {
    if (err) {
        console.log("Connection failed.");
        throw err;
    }
});

const query = 'SELECT VERSION()';

client.query(query)
.then(res => {
   const rows = res.rows;
   console.log(`PostgreSQL version: ${rows[0].version}`);
   client.end();
   process.exit();
}).catch(err => {
   console.log(err);
});

執行的時候使用 node index.js 來執行。

Tcl client side interface

Pgtcl is a Tcl package for client programs to interface with PostgreSQL servers. 建立在 libpq 的基礎之上。

下面是一個最簡單的例子:

package require Pgtcl

set db [pg_connect -conninfo [list host = localhost user = postgres password = postgres dbname = postgres]]

pg_select $db "select version() as versoin" version {
    parray version
}

pg_disconnect $db

和 JDBC 單純的使用 ? 來代表參數不同,Pgtcl/libpq 使用 $1, $2 等來代表要傳遞的參數。


如果要使用共同的資料庫存取介面,建議使用 Tcl 8.6 內建的 TDBC 所提供的 PostgreSQL driver 來與 PostgreSQL server 進行連線。 Pgtcl 只有在單純使用 PostgreSQL 時才需要使用。

要注意的是,TDBC-Postgres 使用 PQprepare 來送出 SQL statement,同時並不指定 type,而是讓 PostgreSQL 來推斷 type, 再使用 PQdescribePrepared 來取得相關的 param 資料。但是 Pgtcl 不支援 PQprepare(只送出 SQL statement 到 server 分析), 所以需要使用 PREPARE statement 才行。

PostgreSQL: Foreign data wrappers

In 2003, a new specification called SQL/MED ("SQL Management of External Data") was added to the SQL standard. It is a standardized way of handling access to remote objects from SQL databases. In 2011, PostgreSQL 9.1 was released with read-only support of this standard, and in 2013 write support was added with PostgreSQL 9.3.

在使用 FDW 的情況下,PostgreSQL 具有存取外部資料源的能力,並且可以作為一個 SQL query engine 使用。 也就是在 FDW 的處理範圍內,PostgreSQL 也有讀取 Hadoop/HBase big data store 的能力(看 FDW 所提供的能力而定)。

PostgreSQL FDW 的 Column-Oriented Wrappers 有二個選擇, 一個是 monetdb_fdw, 一個是 cstore_fdw

monetdb_fdw

使用 monetdb_fdw 來進行 PostgreSQL Foreign_data_wrappers 的測試。

如果不從 PostgreSQL source code 一起編譯,使用下列的方式:
make USE_PGXS=1
如果 MonetDB 的 MAPI lib 與 include 目錄不正確,需要修改 Makefile。

下面是 Makefile 修改的範例:
SHLIB_LINK = -L/usr/local/lib64 -lmapi
PG_CPPFLAGS = -I/usr/local/include/monetdb

再來是安裝:
sudo make USE_PGXS=1 install
需要修改 /var/lib/pgsql/data/postgresql.conf,加入下列的設定:
shared_preload_libraries = 'monetdb_fdw'

要注意的是,重新啟動 PostgreSQL 前要確定 monetdb_fdw 可以正確的尋找到 MonetDB MAPI library 的路徑位置, 否則要設定才行,不然 PostgreSQL 會無法正確啟動

下面是一個例子:
export LD_LIBRARY_PATH=/usr/local/lib64:$LD_LIBRARY_PATH
然後重新啟動 PostgreSQL。


下面是使用 psql 來進行是否可以使用的測試:
CREATE EXTENSION monetdb_fdw;
再來是建立 server object:
CREATE SERVER monetdb_server FOREIGN DATA WRAPPER monetdb_fdw;
然後建立與 MonetDB table 對應的 foreign table。假設 MonetDB 有一個 person 的 table, 我們可以對應過來以後,就可以對這個表格進行操作。

下面是一個例子:
CREATE FOREIGN TABLE person (id integer, name varchar(40)) SERVER monetdb_server
OPTIONS (host 'localhost', port '50000', user 'monetdb', passwd 'monetdb', dbname 'demo', table 'person');
然後就可以查詢 MonetDB person 表格:
SELECT * FROM person;

如果要刪除不用的 FDW 並且完整的移除,要先刪除全部的 Foreign table:
DROP FOREIGN TABLE person;
Then drop the monetdb server and extension:
DROP SERVER monetdb_server;
DROP EXTENSION monetdb_fdw;
Then remove monetdb_fdw from shared_preload_libraries in your postgresql.conf:
#shared_preload_libraries = 'monetdb_fdw'
Finally remove the monetdb_fdw files, into the monetdb_fdw development folder, execute:
sudo make USE_PGXS=1 uninstall

openSUSE 切換輸入法框架

雖然目前 openSUSE 仍沒有切換輸入法框架的工具,但只要設定好 INPUT_METHOD 這個環境變數,就可以覆蓋原先為不同語言所設定的預設值。 例如可以設定 INPUT_METHOD="ibus",下次登入就會使用 ibus 輸入法框架。 而可用的輸入法框架,可以參考 /etc/X11/xim.d(openSUSE Tumbleweed 的位置在 /usr/etc/X11/xim.d)目錄下的設定。

一般而言,這些設定都是在設定 GTK_IM_MODULE, QT_IM_MODULE 與 XMODIFIERS 這幾個環境變數, 並且啟動 input method server。以 fcitx5 為例:

#make sure set these vars before dbus-launch
export LC_CTYPE=$LANG
export XMODIFIERS="@im=fcitx"
export GTK_IM_MODULE=fcitx
export QT_IM_SWITCHER=imsw-multi
export QT_IM_MODULE=fcitx

# FIXME: a little bit of delay is required to avoid race (boo#947576)
(sleep 2; fcitx5 -d) &

# success:
return 0

在 openSUSE leap 42.1 以及之後的環境(包含 openSUSE Tumbleweed), INPUT_METHOD 環境變數可以在 ~/.i18n 中設定。下面是 iBus 的設定範例。

#
# Override system wide input method here
#

export INPUT_METHOD=ibus

然後重新登入就會使用指定的輸入法框架。

ibus 的設定(包含輸入法引擎)使用 gconf 來儲存,可以用來觀察目前有的選項,如果因為某些因素需要透過非設定程式的方式修改, 或者在開發程式的過程中想要刪除自己開發過程中最後不用的設定項目,可以使用 dconf-editor 來修改。 dconf-editor 安裝的方式如下:

sudo zypper in dconf-editor

在 openSUSE Leap 15.5 因為修改 systemd xdg autostart 啟動方式,導致 ibus 在 KDE 無法正確啟動, 按照 Bug 1211977 的暫時解法, 需要修改二個檔案:

/usr/bin/ibus-autostart

# boo#1211977: xdg-autostart-generator cannot launch ibus with -d
if [ "$XDG_CURRENT_DESKTOP" = "KDE" ]; then
    exec ibus-daemon --xim
fi

ibus-daemon --xim -d
exit 0
/etc/xdg/autostart/ibus-autostart.desktop
[Desktop Entry]
Name=IBus
GenericName=IBus Daemon
Comment=Start IBus daemon
Exec=ibus-autostart
Icon=ibus-setup
Terminal=false
Type=Application
StartupNotify=false
NoDisplay=true
#X-GNOME-Autostart-Phase=Applications
#X-GNOME-AutoRestart=false
#X-GNOME-Autostart-Notify=false
X-KDE-autostart-after=panel
X-KDE-StartupNotify=false

如果原本沒有安裝,現在要在 openSUSE Tumbleweed 使用 Fcitx 5 並且安裝一些輸入法(包含酷音輸入法), 使用下列的指令:

sudo zypper install fcitx5 fcitx5-table-extra fcitx5-zhuyin fcitx5-chewing

並且在 ~/.i18n 中設定 :

#
# Override system wide input method here
#

export INPUT_METHOD=fcitx5

openSUSE Tumbleweed 可以使用上述的方式加入 Fcitx 5。

另外,.i18n 這個檔案除了可以用來設定輸入法框架,也可以用來設定 locale。 對於 Fcitx 5 而言,除了 /etc/locale.conf,還可以在設定 INPUT_METHOD 環境變數之前設定 LANG, 即可設定 Fcitx 5 的 locale。 Fcitx5 使用系統設定,而不是桌面系統的設定決定使用何種 locale,如果你二者的設定不同,可以在這裡設定你想要的。

另外,Fcitx 5 有個麻煩的地方,就是目前家目錄設定 .i18n 的方式如果切換到其它輸入法框架,Fcitx 5 會對其它的框架造成干擾。 原因是 Fcitx 5 套件會在 /etc/xdg/autostart 放檔案 org.fcitx.Fcitx5, 即使目前的輸入法框架不是使用 Fcitx 5 一樣會自動啟動,如果有需要使用其它的輸入法框架在檔案 org.fcitx.Fcitx5 加入下列的設定。

Hidden=true

下面的小知識與如何切換輸入法框架無關。如果需要檢查 keyboard event(press and release), 在 X Window 系統下可以使用 xev 指令檢查:

xev -event keyboard

如果是在 Wayland 系統,則可以使用 wev 指令檢查。