Module: PGMQ::QueueName

Defined in:
lib/pgmq/queue_name.rb

Overview

Queue name validation, normalization, and sanitization.

PGMQ interpolates a queue’s name into PostgreSQL identifiers (it creates tables named pgmq.q_<name> and pgmq.a_<name>), so a name has to be a valid, length-bounded SQL identifier. This module is the single source of truth for those rules and offers tiers depending on how much you trust the input:

  1. QueueName.validate! / QueueName.valid? - assert a name is already valid. Use for names you control. Client calls QueueName.validate! before every operation.

  2. QueueName.normalize - lightly rewrite a name that is meant to be valid but uses a friendlier separator. Maps the common stream separators (hyphens, dots, colons) to underscores, strips any other invalid character, then validates - so a Turbo Stream channel like “chat:room-7” becomes “chat_room_7”. Raises if the result still can’t be a valid name (empty, or starts with a digit).

  3. QueueName.sanitize! - coerce untrusted input into a valid name by stripping every invalid character, then validate. Raises if nothing valid remains. Use this as a SQL-identifier guard: the result is always either a name you know is safe, or an exception - never a silent substitute.

  4. QueueName.sanitize - the lenient sibling of QueueName.sanitize!: best-effort coercion that never raises for content and always returns a usable identifier (falling back to a default). Convenient, but see the collision caveat on the method - distinct inputs can map to the same name.

Examples:

PGMQ::QueueName.valid?("orders")           # => true
PGMQ::QueueName.validate!("my-queue")      # => raises InvalidQueueNameError
PGMQ::QueueName.normalize("chat:room-7")   # => "chat_room_7"
PGMQ::QueueName.sanitize!("orders!!")      # => "orders"
PGMQ::QueueName.sanitize!("!!!")           # => raises InvalidQueueNameError
PGMQ::QueueName.sanitize("99 Problems!")   # => "q_99_problems"

Constant Summary collapse

MAX_LENGTH =

Maximum queue name length. PGMQ creates tables with prefixes (q_, a_) and PostgreSQL caps identifiers at 63 characters; PGMQ enforces 48 to leave room for those prefixes and suffixes.

48
PATTERN =

A valid queue name: starts with a letter or underscore, then letters, digits, or underscores.

/\A[a-zA-Z_][a-zA-Z0-9_]*\z/
SANITIZE_PREFIX =

Prefix used by sanitize when the input would otherwise start with an illegal leading character (e.g. a digit) but still has usable trailing characters.

"q_"
SANITIZE_FALLBACK =

Name sanitize falls back to when the input has no usable characters at all.

"queue"

Class Method Summary collapse

Class Method Details

.normalize(name) ⇒ String

Rewrites a name that is meant to be valid but uses friendlier separators, then validates it.

Maps the common stream-name separators - hyphens, dots, and colons - to underscores, strips any other invalid character, then collapses repeated underscores and trims them from the ends. So “chat:room-7” becomes “chat_room_7” and “order.events” becomes “order_events”, while a stray “a@b” becomes “ab” (the @ is dropped, not turned into a separator). The result is validated, so names that still can’t be valid (empty, or starting with a digit) raise rather than being silently mangled.

Colons in particular are the turbo-rails stream-name separator, so they are mapped to a safe character rather than stripped - otherwise “a:b” and “ab” would collide on the same queue.

Parameters:

  • name (String, #to_s)

    a name using friendly separators

Returns:

  • (String)

    the normalized, validated queue name

Raises:



95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/pgmq/queue_name.rb', line 95

def normalize(name)
  str = name.to_s
  return validate!(str) if str.match?(PATTERN)

  normalized = str
    .gsub(/[-.:]/, "_")          # hyphens / dots / colons -> underscores
    .gsub(/[^a-zA-Z0-9_]/, "")   # strip any remaining invalid character
    .squeeze("_")                # collapse consecutive underscores
    .gsub(/\A_+|_+\z/, "")       # trim leading / trailing underscores

  validate!(normalized)
end

.sanitize(name) ⇒ String

Note:

Because it coerces rather than rejects, distinct inputs can map to the same name (e.g. “a/b” and “a-b” both become “a_b”; “!!!” and “” both become “queue”). If your name selects a queue table, that means two logically different inputs could share one queue. When that matters - especially for untrusted input - prefer sanitize!, which raises instead of substituting.

Best-effort coercion of arbitrary input into a valid queue name; the lenient sibling of sanitize!.

Never raises for content: it lowercases, replaces every illegal character run with an underscore, trims underscores, prefixes SANITIZE_PREFIX when the first surviving character is not a valid identifier start (e.g. a digit), truncates to fit MAX_LENGTH, and falls back to SANITIZE_FALLBACK when nothing usable remains. The return value always satisfies valid?.

Parameters:

  • name (String, #to_s)

    arbitrary input

Returns:

  • (String)

    a guaranteed-valid queue name



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/pgmq/queue_name.rb', line 136

def sanitize(name)
  cleaned = name.to_s.downcase
    .gsub(/[^a-z0-9_]+/, "_").squeeze("_")
    .gsub(/\A_+|_+\z/, "")

  # Nothing usable survived the scrub - use the fallback rather than emit a bare prefix like "q_".
  return SANITIZE_FALLBACK if cleaned.empty?

  # A valid identifier can't start with a digit; prefix so the leading character is legal.
  cleaned = "#{SANITIZE_PREFIX}#{cleaned}" unless cleaned.match?(/\A[a-z_]/)

  # Keep under MAX_LENGTH (valid? requires strictly less than), trimming any underscore left at the cut.
  cleaned = cleaned[0, MAX_LENGTH - 1].sub(/_+\z/, "") if cleaned.length >= MAX_LENGTH

  cleaned.empty? ? SANITIZE_FALLBACK : cleaned
end

.sanitize!(name) ⇒ String

Strips every invalid character from untrusted input, then validates the result.

This is the SQL-identifier guard: it removes anything outside [A-Za-z0-9_] and passes the remainder through validate!. If nothing valid remains (empty result, leading digit, too long) it raises, so a caller can never accidentally operate on a different queue than intended. Use this for names from untrusted sources (URL params, external systems) where a wrong-but-valid name would be worse than an error.

Parameters:

  • name (String, #to_s)

    untrusted input

Returns:

  • (String)

    the sanitized, validated queue name

Raises:



118
119
120
# File 'lib/pgmq/queue_name.rb', line 118

def sanitize!(name)
  validate!(name.to_s.gsub(/[^a-zA-Z0-9_]/, ""))
end

.valid?(name) ⇒ Boolean

Returns true if the name is already a valid queue name.

Parameters:

  • name (String, #to_s)

    candidate queue name

Returns:

  • (Boolean)


51
52
53
54
# File 'lib/pgmq/queue_name.rb', line 51

def valid?(name)
  str = name.to_s
  !str.empty? && str.length < MAX_LENGTH && str.match?(PATTERN)
end

.validate!(name) ⇒ String

Validates a queue name, returning it unchanged when valid and raising otherwise.

Parameters:

  • name (String, #to_s)

    candidate queue name

Returns:

  • (String)

    the validated name (as a String)

Raises:



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/pgmq/queue_name.rb', line 61

def validate!(name)
  str = name.to_s

  if name.nil? || str.strip.empty?
    raise Errors::InvalidQueueNameError, "Queue name cannot be empty"
  end

  if str.length >= MAX_LENGTH
    raise Errors::InvalidQueueNameError,
      "Queue name '#{str}' exceeds maximum length of #{MAX_LENGTH} characters " \
      "(current length: #{str.length})"
  end

  return str if str.match?(PATTERN)

  raise Errors::InvalidQueueNameError,
    "Invalid queue name '#{str}': must start with a letter or underscore " \
    "and contain only letters, digits, and underscores"
end